#!/bin/bash # GRE/VXLAN 隧道生成器 # 生成 GRE 或 VXLAN 隧道的 systemd 服务文件 # 支持 IPv4 和 IPv6,IPv6 支持 PBR 防止 bird 路由冲突 set -e # 检测是否通过管道运行 if [[ ! -t 0 ]]; then echo "========================================" echo " GRE/VXLAN 隧道服务生成器" echo "========================================" echo "" echo "检测到您正在通过管道运行此脚本。" echo "由于需要交互式输入,请使用以下方式运行:" echo "" echo " # 下载脚本" echo " curl -fsSL https://raw.githubusercontent.com/yanglc721/tunnelgen/main/tunnel-generator.sh -o tunnel-generator.sh" echo "" echo " # 添加执行权限" echo " chmod +x tunnel-generator.sh" echo "" echo " # 运行脚本" echo " sudo ./tunnel-generator.sh" echo "" exit 1 fi # 输出颜色 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # 无颜色 print_banner() { echo -e "${BLUE}" echo "========================================" echo " GRE/VXLAN 隧道服务生成器" echo "========================================" echo -e "${NC}" } print_info() { echo -e "${GREEN}[信息]${NC} $1" } print_warn() { echo -e "${YELLOW}[警告]${NC} $1" } print_error() { echo -e "${RED}[错误]${NC} $1" } # 检查是否以 root 运行 check_root() { if [[ $EUID -ne 0 ]]; then print_error "此脚本必须以 root 权限运行" exit 1 fi } # 验证 IPv4 地址 validate_ipv4() { local ip=$1 local regex='^([0-9]{1,3}\.){3}[0-9]{1,3}$' if [[ ! $ip =~ $regex ]]; then return 1 fi # 验证每个八位组 IFS='.' read -ra octets <<< "$ip" for octet in "${octets[@]}"; do if [[ $octet -lt 0 ]] || [[ $octet -gt 255 ]]; then return 1 fi done return 0 } # 验证 IPv6 地址 validate_ipv6() { local ip=$1 local regex='^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$|^::$|^::1$|^([0-9a-fA-F]{0,4}:){1,7}:$|^:([0-9a-fA-F]{0,4}:){1,7}$' if [[ $ip =~ $regex ]]; then return 0 fi return 1 } validate_ip() { local ip=$1 local type=$2 if [[ $type == "ipv4" ]]; then validate_ipv4 "$ip" else validate_ipv6 "$ip" fi } # 获取指定 IP 的网络接口 get_interface_for_ip() { local ip=$1 local ip_type=$2 if [[ $ip_type == "ipv4" ]]; then ip -4 route get "$ip" 2>/dev/null | sed -n 's/.*dev \([^ ]*\).*/\1/p' | head -1 else ip -6 route get "$ip" 2>/dev/null | sed -n 's/.*dev \([^ ]*\).*/\1/p' | head -1 fi } # 列出可用的网络接口 list_interfaces() { local ip_type=$1 echo "" echo "可用的网络接口:" echo "=====================================" local idx=0 while IFS= read -r line; do # 提取接口名称 (格式: "2: eth0: <...") local iface=$(echo "$line" | awk -F': ' '{print $2}') if [[ -z "$iface" ]]; then continue fi # 跳过回环接口 if [[ "$iface" == "lo" ]]; then continue fi # 获取接口状态 local state=$(ip link show "$iface" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="state") print $(i+1)}' | head -1) # 跳过未启用的接口 if [[ "$state" != "UP" && "$state" != "UNKNOWN" ]]; then continue fi # 获取 IP 地址 local ipv4_addr=$(ip -4 addr show "$iface" 2>/dev/null | awk '/inet /{print $2}' | cut -d'/' -f1 | head -1) local ipv6_addr=$(ip -6 addr show "$iface" 2>/dev/null | awk '/inet6 / && !/fe80::/{print $2}' | cut -d'/' -f1 | head -1) idx=$((idx + 1)) printf " [%d] %-12s 状态:%-8s" "$idx" "$iface" "$state" if [[ $ip_type == "ipv4" ]]; then if [[ -n "$ipv4_addr" ]]; then printf " IPv4: %s" "$ipv4_addr" fi else if [[ -n "$ipv6_addr" ]]; then printf " IPv6: %s" "$ipv6_addr" fi fi echo "" done < <(ip link show 2>/dev/null | grep -E '^[0-9]+:') echo "=====================================" echo "" } # 根据索引获取接口 get_interface_by_idx() { local idx=$1 shift local ifaces=("$@") echo "${ifaces[$((idx-1))]}" } # 检查路由表是否存在 check_rt_table() { local table_name=$1 grep -q "^.*${table_name}$" /etc/iproute2/rt_tables 2>/dev/null } # 添加路由表 add_rt_table() { local table_id=$1 local table_name=$2 if ! check_rt_table "$table_name"; then echo "${table_id} ${table_name}" >> /etc/iproute2/rt_tables print_info "已创建路由表: ${table_name} (ID: ${table_id})" else print_info "路由表已存在: ${table_name}" fi } # 获取接口的默认网关 get_default_gateway() { local interface=$1 local ip_type=$2 if [[ $ip_type == "ipv4" ]]; then local gw=$(ip -4 route show default dev "$interface" 2>/dev/null | awk '{print $3}') if [[ -n "$gw" ]]; then echo "$gw" return fi else local gw=$(ip -6 route show default dev "$interface" 2>/dev/null | awk '{print $3}') if [[ -n "$gw" ]]; then echo "$gw" return fi fi } # 获取指定目标 IP 的网关(更准确) get_gateway_for_ip() { local dest_ip=$1 local ip_type=$2 if [[ $ip_type == "ipv4" ]]; then ip -4 route get "$dest_ip" 2>/dev/null | sed -n 's/.*via \([^ ]*\).*/\1/p' | head -1 else ip -6 route get "$dest_ip" 2>/dev/null | sed -n 's/.*via \([^ ]*\).*/\1/p' | head -1 fi } # 生成 GRE 隧道服务文件 generate_gre_service() { local tunnel_name=$1 local remote_ip=$2 local local_ip=$3 local ip_type=$4 local interface=$5 local gateway=$6 local pbr_table="${tunnel_name}_underlay" local pbr_table_id=100 local service_file="/etc/systemd/system/${tunnel_name}.service" local description="GRE 隧道 ${tunnel_name}" # 确定隧道模式 local tunnel_mode="gre" if [[ $ip_type == "ipv6" ]]; then tunnel_mode="ip6gre" fi print_info "正在生成 GRE 隧道服务: $service_file" # IPv6 需要 PBR,先创建路由表 if [[ $ip_type == "ipv6" ]]; then # 查找可用的表 ID while grep -q "^${pbr_table_id} " /etc/iproute2/rt_tables 2>/dev/null; do pbr_table_id=$((pbr_table_id + 1)) done add_rt_table "$pbr_table_id" "$pbr_table" # 创建 PBR 设置脚本 local prestart_script="/usr/local/sbin/${tunnel_name}-pbr-setup.sh" cat > "$prestart_script" << 'PBREOF' #!/bin/bash # GRE 隧道 PBR 设置脚本 # 创建干净的路由表用于隧道底层流量 set -e TUNNEL_NAME="__TUNNEL_NAME__" REMOTE_IP="__REMOTE_IP__" LOCAL_IP="__LOCAL_IP__" INTERFACE="__INTERFACE__" GATEWAY="__GATEWAY__" TABLE_NAME="__TABLE_NAME__" # 在干净的路由表中添加路由 # 通过物理接口路由到远端隧道端点 if [[ -n "$GATEWAY" ]]; then ip -6 route replace "$REMOTE_IP/128" via "$GATEWAY" dev "$INTERFACE" table "$TABLE_NAME" 2>/dev/null || true else # 如果没有网关,尝试直接使用接口(点对点链路) ip -6 route replace "$REMOTE_IP/128" dev "$INTERFACE" table "$TABLE_NAME" 2>/dev/null || true fi # 添加本地地址路由 ip -6 route replace "$LOCAL_IP/128" dev "$INTERFACE" table "$TABLE_NAME" 2>/dev/null || true # 添加 PBR 规则(优先级 50,在默认 main 表查询 32766 之前) ip -6 rule replace to "$REMOTE_IP" table "$TABLE_NAME" priority 50 2>/dev/null || \ ip -6 rule add to "$REMOTE_IP" table "$TABLE_NAME" priority 50 ip -6 rule replace from "$LOCAL_IP" table "$TABLE_NAME" priority 51 2>/dev/null || \ ip -6 rule add from "$LOCAL_IP" table "$TABLE_NAME" priority 51 echo "已为 $TUNNEL_NAME 安装 PBR 规则" PBREOF # 替换占位符 sed -i "s/__TUNNEL_NAME__/$tunnel_name/g" "$prestart_script" sed -i "s/__REMOTE_IP__/$remote_ip/g" "$prestart_script" sed -i "s/__LOCAL_IP__/$local_ip/g" "$prestart_script" sed -i "s/__INTERFACE__/$interface/g" "$prestart_script" sed -i "s/__GATEWAY__/$gateway/g" "$prestart_script" sed -i "s/__TABLE_NAME__/$pbr_table/g" "$prestart_script" chmod +x "$prestart_script" print_info "已创建 PBR 设置脚本: $prestart_script" fi cat > "$service_file" << EOF [Unit] Description=$description After=network.target network-online.target Wants=network-online.target [Service] Type=oneshot RemainAfterExit=yes EOF # 构建 ExecStart 命令 local exec_start="ip tunnel add $tunnel_name mode $tunnel_mode remote $remote_ip local $local_ip ttl 255 && ip link set $tunnel_name up" if [[ $ip_type == "ipv6" ]]; then # 在创建隧道前添加 PBR 设置 cat >> "$service_file" << EOF ExecStartPre=/usr/local/sbin/${tunnel_name}-pbr-setup.sh EOF fi cat >> "$service_file" << EOF ExecStart=/bin/sh -c "$exec_start" EOF # 构建 ExecStop 命令 local exec_stop="ip link set $tunnel_name down; ip tunnel del $tunnel_name" if [[ $ip_type == "ipv6" ]]; then # 停止时删除 PBR 规则 exec_stop="ip link set $tunnel_name down; ip tunnel del $tunnel_name; ip -6 rule del to $remote_ip table $pbr_table priority 50 || true; ip -6 rule del from $local_ip table $pbr_table priority 51 || true" fi cat >> "$service_file" << EOF ExecStop=/bin/sh -c "$exec_stop" [Install] WantedBy=multi-user.target EOF print_info "服务文件已创建: $service_file" } # 生成 VXLAN 隧道服务文件 generate_vxlan_service() { local tunnel_name=$1 local remote_ip=$2 local local_ip=$3 local dst_port=$4 local vni=$5 local ip_type=$6 local interface=$7 local gateway=$8 local pbr_table="${tunnel_name}_underlay" local pbr_table_id=100 local service_file="/etc/systemd/system/${tunnel_name}.service" local description="VXLAN 隧道 ${tunnel_name}" print_info "正在生成 VXLAN 隧道服务: $service_file" # IPv6 需要 PBR,先创建路由表 if [[ $ip_type == "ipv6" ]]; then # 查找可用的表 ID while grep -q "^${pbr_table_id} " /etc/iproute2/rt_tables 2>/dev/null; do pbr_table_id=$((pbr_table_id + 1)) done add_rt_table "$pbr_table_id" "$pbr_table" # 创建 PBR 设置脚本 local prestart_script="/usr/local/sbin/${tunnel_name}-pbr-setup.sh" cat > "$prestart_script" << 'PBREOF' #!/bin/bash # VXLAN 隧道 PBR 设置脚本 # 创建干净的路由表用于隧道底层流量 set -e TUNNEL_NAME="__TUNNEL_NAME__" REMOTE_IP="__REMOTE_IP__" LOCAL_IP="__LOCAL_IP__" INTERFACE="__INTERFACE__" GATEWAY="__GATEWAY__" TABLE_NAME="__TABLE_NAME__" # 在干净的路由表中添加路由 # 通过物理接口路由到远端隧道端点 if [[ -n "$GATEWAY" ]]; then ip -6 route replace "$REMOTE_IP/128" via "$GATEWAY" dev "$INTERFACE" table "$TABLE_NAME" 2>/dev/null || true else ip -6 route replace "$REMOTE_IP/128" dev "$INTERFACE" table "$TABLE_NAME" 2>/dev/null || true fi # 添加本地地址路由 ip -6 route replace "$LOCAL_IP/128" dev "$INTERFACE" table "$TABLE_NAME" 2>/dev/null || true # 添加 PBR 规则(优先级 50,在默认 main 表查询 32766 之前) ip -6 rule replace to "$REMOTE_IP" table "$TABLE_NAME" priority 50 2>/dev/null || \ ip -6 rule add to "$REMOTE_IP" table "$TABLE_NAME" priority 50 ip -6 rule replace from "$LOCAL_IP" table "$TABLE_NAME" priority 51 2>/dev/null || \ ip -6 rule add from "$LOCAL_IP" table "$TABLE_NAME" priority 51 echo "已为 $TUNNEL_NAME 安装 PBR 规则" PBREOF # 替换占位符 sed -i "s/__TUNNEL_NAME__/$tunnel_name/g" "$prestart_script" sed -i "s/__REMOTE_IP__/$remote_ip/g" "$prestart_script" sed -i "s/__LOCAL_IP__/$local_ip/g" "$prestart_script" sed -i "s/__INTERFACE__/$interface/g" "$prestart_script" sed -i "s/__GATEWAY__/$gateway/g" "$prestart_script" sed -i "s/__TABLE_NAME__/$pbr_table/g" "$prestart_script" chmod +x "$prestart_script" print_info "已创建 PBR 设置脚本: $prestart_script" fi cat > "$service_file" << EOF [Unit] Description=$description After=network.target network-online.target Wants=network-online.target [Service] Type=oneshot RemainAfterExit=yes EOF # 构建 ExecStart 命令 local exec_start="ip link add $tunnel_name type vxlan id $vni dev $interface remote $remote_ip dstport $dst_port learning && ip link set $tunnel_name up" if [[ $ip_type == "ipv6" ]]; then # 在创建隧道前添加 PBR 设置 cat >> "$service_file" << EOF ExecStartPre=/usr/local/sbin/${tunnel_name}-pbr-setup.sh EOF fi cat >> "$service_file" << EOF ExecStart=/bin/sh -c "$exec_start" EOF # 构建 ExecStop 命令 local exec_stop="ip link set $tunnel_name down; ip link del $tunnel_name" if [[ $ip_type == "ipv6" ]]; then # 停止时删除 PBR 规则 exec_stop="ip link set $tunnel_name down; ip link del $tunnel_name; ip -6 rule del to $remote_ip table $pbr_table priority 50 || true; ip -6 rule del from $local_ip table $pbr_table priority 51 || true" fi cat >> "$service_file" << EOF ExecStop=/bin/sh -c "$exec_stop" [Install] WantedBy=multi-user.target EOF print_info "服务文件已创建: $service_file" } # 生成 SSH TUN 隧道服务文件 generate_sshtun_service() { local tunnel_name=$1 local remote_ip=$2 local local_ip=$3 local tunnel_ip_local=$4 local tunnel_ip_remote=$5 local ssh_port=$6 local ssh_user=$7 local role=$8 # "server" or "client" local ip_type=$9 # "ipv4" or "ipv6" local service_file="/etc/systemd/system/${tunnel_name}.service" local description="SSH TUN 隧道 ${tunnel_name}" # 确定掩码 local prefix="24" if [[ "$ip_type" == "ipv6" ]]; then prefix="64" fi print_info "正在生成 SSH TUN 隧道服务: $service_file" if [[ "$role" == "server" ]]; then # 服务端配置 # 检查并配置 SSH PermitTunnel local sshd_config="/etc/ssh/sshd_config" if ! grep -q "^PermitTunnel[[:space:]]*yes" "$sshd_config" 2>/dev/null; then print_info "正在配置 SSH PermitTunnel..." if grep -q "^#*PermitTunnel" "$sshd_config" 2>/dev/null; then sed -i 's/^#*PermitTunnel.*/PermitTunnel yes/' "$sshd_config" else echo "PermitTunnel yes" >> "$sshd_config" fi # 重启 SSH 服务 systemctl restart sshd || systemctl restart ssh print_info "已启用 SSH PermitTunnel 并重启 SSH 服务" fi # 服务端服务文件 - 等待客户端连接 cat > "$service_file" << EOF [Unit] Description=$description (服务端) After=network.target network-online.target sshd.service Wants=network-online.target [Service] Type=simple # 服务端通过 SSH 连接的自动触发,不需要额外服务 # 当客户端连接时会自动创建 tun 设备 # 此服务主要用于记录状态 ExecStartPre=/bin/sh -c 'echo "SSH TUN 服务端已就绪,等待客户端连接..."' ExecStart=/bin/sleep infinity ExecStop=/bin/true [Install] WantedBy=multi-user.target EOF # 创建 IP 配置脚本(连接建立后手动或自动执行) local ip_config_script="/usr/local/sbin/${tunnel_name}-ip-config.sh" cat > "$ip_config_script" << EOF #!/bin/bash # SSH TUN 隧道 IP 配置脚本 # 当 SSH 连接建立后,tun 设备会自动创建 # 运行此脚本配置 IP 地址 # 查找 tun 设备(通常为 tun0) TUN_DEV="" for i in {0..9}; do if ip link show tun\$i &>/dev/null; then TUN_DEV="tun\$i" break fi done if [[ -z "\$TUN_DEV" ]]; then echo "未找到 tun 设备,请确保 SSH 客户端已连接" exit 1 fi echo "配置 \$TUN_DEV IP 地址..." ip addr add ${tunnel_ip_local}/${prefix} dev \$TUN_DEV ip link set \$TUN_DEV up echo "SSH TUN 隧道 IP 配置完成" echo "本地隧道 IP: ${tunnel_ip_local}" echo "对端隧道 IP: ${tunnel_ip_remote}" EOF chmod +x "$ip_config_script" print_info "已创建 IP 配置脚本: $ip_config_script" else # 客户端配置 # 检查 SSH 密钥 local ssh_key="" if [[ -f "/root/.ssh/id_rsa" ]]; then ssh_key="/root/.ssh/id_rsa" elif [[ -f "/root/.ssh/id_ed25519" ]]; then ssh_key="/root/.ssh/id_ed25519" elif [[ -f "/root/.ssh/id_ecdsa" ]]; then ssh_key="/root/.ssh/id_ecdsa" fi if [[ -z "$ssh_key" ]]; then print_warn "未找到 SSH 密钥,将生成新的密钥..." ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N "" ssh_key="/root/.ssh/id_ed25519" print_info "已生成 SSH 密钥: $ssh_key" print_warn "请将公钥添加到服务端:" echo "" cat "${ssh_key}.pub" echo "" read -p "已将公钥添加到服务端? [y/N]: " key_added if [[ ! "$key_added" =~ ^[Yy]$ ]]; then print_info "请先添加公钥后再运行此脚本" exit 0 fi fi # 客户端服务文件 cat > "$service_file" << EOF [Unit] Description=$description (客户端) After=network.target network-online.target Wants=network-online.target [Service] Type=simple User=root # SSH TUN 连接命令 # -w 0:0 创建 tun 设备 # -o Tunnel=point-to-point 点对点模式 # -o ServerAliveInterval 保持连接 # -o ExitOnForwardFailure 连接失败时退出 ExecStart=/usr/bin/ssh -i ${ssh_key} \\ -w 0:0 \\ -o Tunnel=point-to-point \\ -o ServerAliveInterval=30 \\ -o ServerAliveCountMax=3 \\ -o ExitOnForwardFailure=yes \\ -o StrictHostKeyChecking=accept-new \\ -p ${ssh_port} \\ ${ssh_user}@${remote_ip} \\ -N # 连接建立后配置 IP ExecStartPost=/bin/sh -c 'sleep 2 && ip addr add ${tunnel_ip_local}/${prefix} dev tun0 && ip link set tun0 up' # 重启策略 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF fi print_info "服务文件已创建: $service_file" } # 主脚本 main() { print_banner # 检查 root check_root # 选择 IP 版本 echo "" echo "选择 IP 版本:" echo " 1) IPv4" echo " 2) IPv6" echo "" read -p "请输入选项 [1-2]: " ip_choice case $ip_choice in 1) ip_type="ipv4" ;; 2) ip_type="ipv6" ;; *) print_error "无效的选项"; exit 1 ;; esac print_info "已选择: $ip_type" # SSH TUN 使用 IPv4 隧道 IP,跳过 IPv6 特有配置 # 但仍需要 ip_type 变量 # 选择隧道类型 echo "" echo "选择隧道类型:" echo " 1) GRE" echo " 2) VXLAN" echo " 3) SSH TUN (TCP 隧道,适用于 UDP 受限环境)" echo "" read -p "请输入选项 [1-3]: " tunnel_choice case $tunnel_choice in 1) tunnel_type="gre" ;; 2) tunnel_type="vxlan" ;; 3) tunnel_type="sshtun" ;; *) print_error "无效的选项"; exit 1 ;; esac print_info "已选择: $tunnel_type" # 获取隧道名称 echo "" read -p "请输入隧道名称 (例如: gre-hk1, vxlan-hk4, ssh-ca): " tunnel_name if [[ -z "$tunnel_name" ]]; then print_error "隧道名称不能为空" exit 1 fi # 检查服务是否已存在 if [[ -f "/etc/systemd/system/${tunnel_name}.service" ]]; then print_warn "服务文件已存在: /etc/systemd/system/${tunnel_name}.service" read -p "是否覆盖? [y/N]: " overwrite if [[ ! "$overwrite" =~ ^[Yy]$ ]]; then print_info "已取消" exit 0 fi fi # 获取本地 IP echo "" if [[ $tunnel_type == "sshtun" ]]; then # SSH TUN 需要知道角色 echo "选择本机角色:" echo " 1) 服务端 (有公网 IP,等待客户端连接)" echo " 2) 客户端 (在 NAT 内,主动连接服务端)" echo "" read -p "请输入选项 [1-2]: " role_choice case $role_choice in 1) ssh_role="server" ;; 2) ssh_role="client" ;; *) print_error "无效的选项"; exit 1 ;; esac print_info "已选择角色: $ssh_role" # 获取对端 IP echo "" if [[ $ssh_role == "client" ]]; then read -p "请输入服务端 IP 地址: " remote_ip else read -p "请输入客户端公网 IP 地址 (可选,用于防火墙规则): " remote_ip fi # SSH 参数 echo "" read -p "请输入 SSH 端口 [默认: 22]: " ssh_port ssh_port=${ssh_port:-22} read -p "请输入 SSH 用户 [默认: root]: " ssh_user ssh_user=${ssh_user:-root} # 隧道 IP 类型 echo "" echo "选择隧道内部 IP 类型:" echo " 1) IPv4 (10.0.0.x)" echo " 2) IPv6 (fd00::x)" echo "" read -p "请输入选项 [1-2,默认 2]: " tunnel_ip_choice tunnel_ip_choice=${tunnel_ip_choice:-2} case $tunnel_ip_choice in 1) tunnel_ip_type="ipv4" default_local="10.0.0.1" default_remote="10.0.0.2" ;; 2) tunnel_ip_type="ipv6" default_local="fd00::1" default_remote="fd00::2" ;; *) print_error "无效的选项"; exit 1 ;; esac print_info "已选择隧道 IP 类型: $tunnel_ip_type" # 隧道 IP echo "" print_info "配置隧道内部 IP 地址:" read -p "请输入本地隧道 IP [默认: $default_local]: " tunnel_ip_local tunnel_ip_local=${tunnel_ip_local:-$default_local} read -p "请输入对端隧道 IP [默认: $default_remote]: " tunnel_ip_remote tunnel_ip_remote=${tunnel_ip_remote:-$default_remote} # 验证 IP if [[ $tunnel_ip_type == "ipv4" ]]; then if ! validate_ipv4 "$tunnel_ip_local" || ! validate_ipv4 "$tunnel_ip_remote"; then print_error "无效的 IPv4 地址" exit 1 fi else if ! validate_ipv6 "$tunnel_ip_local" || ! validate_ipv6 "$tunnel_ip_remote"; then print_error "无效的 IPv6 地址" exit 1 fi fi # 如果是客户端,交换 IP if [[ $ssh_role == "client" ]]; then local temp=$tunnel_ip_local tunnel_ip_local=$tunnel_ip_remote tunnel_ip_remote=$temp fi else # GRE/VXLAN 原有逻辑 if [[ $ip_type == "ipv4" ]]; then read -p "请输入本地 IPv4 地址: " local_ip if ! validate_ipv4 "$local_ip"; then print_error "无效的 IPv4 地址" exit 1 fi else read -p "请输入本地 IPv6 地址: " local_ip if ! validate_ipv6 "$local_ip"; then print_error "无效的 IPv6 地址" exit 1 fi fi # 获取远端 IP echo "" if [[ $ip_type == "ipv4" ]]; then read -p "请输入远端 IPv4 地址: " remote_ip if ! validate_ipv4 "$remote_ip"; then print_error "无效的 IPv4 地址" exit 1 fi else read -p "请输入远端 IPv6 地址: " remote_ip if ! validate_ipv6 "$remote_ip"; then print_error "无效的 IPv6 地址" exit 1 fi fi fi # 获取接口 - GRE/VXLAN 需要 local interface="" local gateway="" if [[ $tunnel_type != "sshtun" ]]; then echo "" print_info "选择隧道底层网络接口:" # 列出可用接口 list_interfaces "$ip_type" # 根据远端 IP 尝试自动检测接口 auto_interface=$(get_interface_for_ip "$remote_ip" "$ip_type") if [[ -n "$auto_interface" ]]; then print_info "自动检测到 $remote_ip 的接口: $auto_interface" echo "" read -p "按回车使用 [$auto_interface],或输入其他接口名: " manual_iface if [[ -n "$manual_iface" ]]; then interface="$manual_iface" else interface="$auto_interface" fi else read -p "请输入接口名称 (例如: ens18, eth0): " interface fi if [[ -z "$interface" ]]; then print_error "接口不能为空" exit 1 fi # 验证接口存在 if ! ip link show "$interface" &>/dev/null; then print_error "接口 '$interface' 不存在" exit 1 fi print_info "已选择接口: $interface" # IPv6 需要网关用于 PBR if [[ $ip_type == "ipv6" ]]; then echo "" # 方法1: 获取特定远端 IP 的网关(最准确) gateway=$(get_gateway_for_ip "$remote_ip" "$ip_type") # 方法2: 回退到默认网关 if [[ -z "$gateway" ]]; then gateway=$(get_default_gateway "$interface" "$ip_type") fi if [[ -n "$gateway" ]]; then print_info "自动检测到网关: $gateway" read -p "按回车使用此网关,或输入其他网关: " custom_gw if [[ -n "$custom_gw" ]]; then gateway="$custom_gw" fi else print_warn "无法自动检测网关" read -p "请输入网关 IPv6 地址 (点对点链路可留空): " gateway fi print_info "PBR 网关: ${gateway:-<无,直接使用接口>}" fi fi if [[ $tunnel_type == "vxlan" ]]; then # 获取 VXLAN 端口 echo "" read -p "请输入 VXLAN 目标端口 [默认: 4789]: " dst_port dst_port=${dst_port:-4789} # 获取 VNI echo "" read -p "请输入 VXLAN 网络标识符 (VNI) [默认: 100]: " vni vni=${vni:-100} fi # 确认设置 echo "" echo "========================================" echo "配置摘要:" echo "========================================" echo "隧道名称: $tunnel_name" echo "隧道类型: $tunnel_type" if [[ $tunnel_type == "sshtun" ]]; then echo "角色: $ssh_role" echo "对端 IP: ${remote_ip:-<可选>}" echo "SSH 端口: $ssh_port" echo "SSH 用户: $ssh_user" echo "隧道 IP 类型: $tunnel_ip_type" echo "本地隧道 IP: $tunnel_ip_local" echo "对端隧道 IP: $tunnel_ip_remote" else echo "IP 版本: $ip_type" echo "本地 IP: $local_ip" echo "远端 IP: $remote_ip" echo "接口: $interface" if [[ $tunnel_type == "vxlan" ]]; then echo "目标端口: $dst_port" echo "VNI: $vni" fi if [[ $ip_type == "ipv6" ]]; then echo "网关: ${gateway:-<无>}" echo "PBR: 已启用 (使用专用路由表)" fi fi echo "========================================" echo "" read -p "生成服务文件? [Y/n]: " confirm if [[ "$confirm" =~ ^[Nn]$ ]]; then print_info "已取消" exit 0 fi # 生成服务文件 if [[ $tunnel_type == "gre" ]]; then generate_gre_service "$tunnel_name" "$remote_ip" "$local_ip" "$ip_type" "$interface" "$gateway" elif [[ $tunnel_type == "vxlan" ]]; then generate_vxlan_service "$tunnel_name" "$remote_ip" "$local_ip" "$dst_port" "$vni" "$ip_type" "$interface" "$gateway" elif [[ $tunnel_type == "sshtun" ]]; then local local_ip="" [[ -z "$remote_ip" ]] && remote_ip="none" generate_sshtun_service "$tunnel_name" "$remote_ip" "$local_ip" "$tunnel_ip_local" "$tunnel_ip_remote" "$ssh_port" "$ssh_user" "$ssh_role" "$tunnel_ip_type" fi # 显示生成的文件 echo "" echo "========================================" echo "生成的服务文件:" echo "========================================" cat "/etc/systemd/system/${tunnel_name}.service" echo "========================================" echo "" # 询问是否启用并启动 read -p "立即启用并启动服务? [Y/n]: " enable_now if [[ ! "$enable_now" =~ ^[Nn]$ ]]; then systemctl daemon-reload systemctl enable "${tunnel_name}.service" systemctl start "${tunnel_name}.service" echo "" systemctl status "${tunnel_name}.service" --no-pager # 显示隧道接口 echo "" if [[ $tunnel_type == "sshtun" ]]; then if [[ $ssh_role == "server" ]]; then print_info "SSH TUN 服务端已就绪" echo "" echo "下一步操作:" echo " 1. 确保客户端的 SSH 公钥已添加到本机" echo " 客户端运行: cat ~/.ssh/id_ed25519.pub" echo " 本机执行: echo '<公钥>' >> /root/.ssh/authorized_keys" echo "" echo " 2. 客户端连接后,运行以下命令配置 IP:" echo " /usr/local/sbin/${tunnel_name}-ip-config.sh" echo "" echo " 3. 验证隧道:" echo " ping $tunnel_ip_remote" else print_info "SSH TUN 客户端服务已启动" echo "" # 检查 tun0 是否存在 sleep 3 if ip link show tun0 &>/dev/null; then print_info "隧道接口 tun0 已创建" ip addr show tun0 echo "" echo "验证隧道:" echo " ping $tunnel_ip_remote" else print_warn "隧道接口尚未创建,请检查 SSH 连接" fi fi else print_info "隧道接口状态:" ip link show "$tunnel_name" 2>/dev/null || print_warn "接口未找到 (可能需要手动配置)" if [[ $ip_type == "ipv6" ]]; then echo "" print_info "IPv6 PBR 规则 (专用表: ${tunnel_name}_underlay):" ip -6 rule list priority 50 2>/dev/null || print_warn "优先级 50 没有 PBR 规则" ip -6 rule list priority 51 2>/dev/null || print_warn "优先级 51 没有 PBR 规则" echo "" print_info "${tunnel_name}_underlay 表中的路由:" ip -6 route show table "${tunnel_name}_underlay" 2>/dev/null || print_warn "表中没有路由" fi fi else print_info "手动启用和启动命令:" echo " systemctl daemon-reload" echo " systemctl enable ${tunnel_name}.service" echo " systemctl start ${tunnel_name}.service" fi # IPv6 与 bird 的额外说明 (仅 GRE/VXLAN) if [[ $tunnel_type != "sshtun" ]] && [[ $ip_type == "ipv6" ]]; then echo "" print_info "IPv6 PBR 说明 (bird 兼容性):" echo "" echo " 已创建专用路由表 '${tunnel_name}_underlay'" echo " 用于防止 bird 覆盖隧道端点路由。" echo "" echo " 工作原理:" echo " 1. 到/从隧道端点的流量使用干净的路由表" echo " 2. 此表不受 bird 路由通告的影响" echo " 3. PBR 规则优先级 50-51 (在 main 表 32766 之前)" echo "" echo " 验证 PBR 是否工作:" echo " ip -6 route get $remote_ip" echo " # 应显示: dev $interface (物理接口),而不是 dev $tunnel_name" echo "" echo " 检查 PBR 规则:" echo " ip -6 rule show | grep priority" echo "" echo " 检查干净表中的路由:" echo " ip -6 route show table ${tunnel_name}_underlay" echo "" echo " 重要: 如果物理网关变更,请更新:" echo " /usr/local/sbin/${tunnel_name}-pbr-setup.sh" echo " 然后重启服务: systemctl restart $tunnel_name" fi # SSH TUN 额外说明 if [[ $tunnel_type == "sshtun" ]]; then echo "" print_info "SSH TUN 隧道说明:" echo "" echo " 特点:" echo " - 基于 SSH 协议,使用 TCP" echo " - 适用于 UDP 受限的环境" echo " - 客户端主动连接服务端" echo "" echo " 性能:" echo " - 由于 SSH 加密,性能低于 GRE/VXLAN" echo " - 典型速度: 100-500 Mbps" echo "" echo " 注意事项:" echo " - 客户端服务会自动重连" echo " - 服务端需要手动配置 IP (运行 IP 配置脚本)" echo " - 确保 SSH 密钥认证已配置" fi print_info "完成!" } # 运行主函数 main "$@"