在 GRE 隧道建立后,通过策略路由将云服务器 A 的所有出站流量强制转发至云服务器 B 。
基本参数:
- gre 隧道名称:tun1 
- 服务器 A: - 内网 IP:172.18.99.162(172.18.96.0/20) 
- 对等 IP:172.16.0.1 
- 公网 IP:1.2.3.4 
 
- 服务器 B: - 内网 IP:172.30.0.7(172.30.0.0/20) 
- 对等 IP:172.16.0.2 
- 公网 IP:114.67.200.161 
 
网络架构拓扑表述:
云服务器A (172.18.99.162)
        ↓ (所有流量)
GRE隧道 (tun1: 172.16.0.1 ↔ 172.16.0.2)
        ↓
云服务器B (114.67.200.161) → 互联网一、发送方配置(服务器 A)
直连隧道,服务器 B 收到隧道数据包,解封装看到的源 IP 是 172.16.0.1(tun1 IP);如果是策略路由,解封装看到的源 IP 是服务器 A 的内网 IP 172.18.99.162。
注意后面 iptables 配置转发策略,源 IP 不要搞错了。
1、直连隧道
简单,推荐测试使用。
# 使用 tun1 设备
curl --interface tun1 http://www.baidu.com
# 详细信息
curl --interface tun1 -v http://www.baidu.com
# 发送 ping, 百度 ip
ping -I tun1 183.2.172.17
2、策略路由
使用百度IP测试:查看IP:curl -v www.baidu.com,可以看到IP:183.2.172.177
(1)直接添加主路由表
ip route add 183.2.172.177 via 172.16.0.2 dev tun1(2)基于目标 IP 标记
# 一、创建自定义路由表 custom_table 
echo "100 custom_table" >> /etc/iproute2/rt_tables
# 二、在 custom_table 中添加到 测试IP(百度IP) 的路由
ip route add 183.2.172.177 via 172.16.0.2 dev tun1 table custom_table
# 三、定义 rule 策略: 
# 标记到 183.2.172.177 的流量 =》 使用自定义路由表
iptables -t mangle -A OUTPUT -d 183.2.172.177 -j MARK --set-mark 1
ip rule add fwmark 1 table custom_table
(3)查看路由决策
ip route get 183.2.172.177
# 输出结果:
# 路由配置前,使用 eth0
183.2.172.177 via 172.18.111.253 dev eth0 src 172.18.99.162 
# 路由配置后,使用 tun1
183.2.172.177 via 172.16.0.2 dev tun1 src 172.16.0.1 (4)参数解释
以下是旧的方案 —— 基于源 IP 标记,存在缺陷,遂抛弃。相关参数的解释可以参考参考,还有最后有配置恢复(重置)的介绍。
2.1 创建路由表
echo "100 gre-table" >> /etc/iproute2/rt_tables作用:
- /etc/iproute2/rt_tables 是 Linux 系统的路由表配置文件 
- 系统默认有多个路由表: - 255 local - 本地路由表 
- 254 main - 主路由表 
- 253 default - 默认路由表 
 
我们添加 100 gre-table 创建一个编号为 100,名为"gre-table"的自定义路由表
验证:
cat /etc/iproute2/rt_tables
# 应该能看到:
# 100 gre-table
2.2 配置策略路由
第一步:在新路由表中添加默认路由
ip route add default via 172.16.0.1 dev tun1 table gre-table参数解释:
- default - 表示默认路由(0.0.0.0/0) 
- via 172.16.0.1 - 下一跳地址是GRE隧道对端 
- dev tun1 - 出口设备是GRE隧道接口 
- table gre-table - 将这条路由添加到自定义路由表 
验证:
ip route show table gre-table
# 输出应该类似:
# default via 172.16.0.1 dev tun1
第二步:添加策略规则
# 一、添加排除 SSH 的规则(使用fwmark)
iptables -t mangle -A OUTPUT -p tcp --sport 22 -j MARK --set-mark 1
ip rule add fwmark 1 table main priority 999
# 二、源IP、源IP段 配置有风险:172.30.0.7 所有请求都发送到 gre,包括 SSH 请求
# 这会导致服务器登陆不上。建议配置目标IP,而不是源IP
# 方法1:针对单个IP(不推荐)
ip rule add from 172.30.0.7 table gre-table priority 1000
# 方法2:针对整个网段(不推荐) 
ip rule add from 172.30.0.0/20 table gre-table priority 1000参数解释:
- from 172.30.0.7 - 匹配源IP为 172.30.0.7 的数据包 
- from 172.30.0.0/20 - 匹配源IP在 172.30.0.0/20 网段的数据包 
- table gre-table - 匹配的数据包使用 gre-table 路由表 
- priority 1000 - 规则优先级(数字越小优先级越高) 
验证规则:
ip rule show
# 输出应该类似:
# 0:      from all lookup local
# 1000:   from 172.30.0.0/20 lookup gre-table
# 32766:  from all lookup main
# 32767:  from all lookup default2.3 完整的工作流程
当 A 服务器curl www.baidu.com 时:
- 系统生成数据包,源IP为 172.30.0.7 
- ip rule 匹配到源IP在 172.30.0.0/20 网段 
- 系统使用 gre-table 路由表进行路由查找 
- 在 gre-table 中找到默认路由:通过 tun1 发送到 172.16.0.1 
- 数据包通过 GRE 隧道发送到 B 服务器 
2.4 恢复原状
测试完之后,使用下面命令恢复:
# 1、删除策略规则
ip rule del from 172.30.0.0/20 table gre-table priority 1000
# 1.1 或者删除指定优先级的规则
ip rule del priority 1000
# 2、删除路由表中的路由
ip route del default table gre-table
# 3、可选:删除路由表定义(需要编辑文件)
sed -i '/gre-table/d' /etc/iproute2/rt_tables
验证:
# 检查规则是否删除
ip rule show | grep gre-table
# 检查路由表是否为空
ip route show table gre-table
# 测试网络连通性
curl www.baidu.com
# 现在应该走原来的默认路由,而不是GRE隧道3、DNS 重写
服务器 A 如果想通过 tun1 访问服务器 B 的公网 IP,可以 DNS 重写:
不过一般用不上。
# 公网 IP 用于构建 tun1 隧道,请不要把公网指向 tun1, 有路由环路问题
# ip route add 114.67.200.161 via 172.16.0.2 dev tun1
# 1. 在 /etc/hosts 中将 域名B 指向 B 的内网IP
echo "172.30.0.7 your-domain.com" >> /etc/hosts
# 达成效果:
# your-domain.com -> 114.67.200.161 -> eth0
# your-domain.com -> 172.30.0.7 -> tun1二、接收、转发(服务器 B)
注意:需要正确处理隧道请求,不然会因为找不到目标而丢弃数据包,也就是回程数据包可能无法正确返回。
需要开启 IP 转发:
- 数据包目标IP不是本机 
- 需要从GRE接口转发到内网接口 
# 开启转发(临时生效)
echo 1 > /proc/sys/net/ipv4/ip_forward
# 隧道搭建一文中提到的修改 /etc/sysctl.conf 是永久生效实际的数据包流向:
设备(10.6.0.1) → GRE封装 → 阿里云(解封装) → 根据路由/防火墙规则处理
                                      ├→ 内网其他服务器
                                      ├→ 本机服务(localhost:8080)  
                                      ├→ 公网互联网
                                      └→ 其他GRE隧道1、转发到内网
解封装后转发到内网机器,不需要改目标地址则不需要做任何配置。
设备(10.6.0.1) → GRE隧道 → 阿里云(解封装) → 目标:内网服务器(172.30.0.0/20)
                                                      ↑
                                              本机作为网关,需要转发2、转发到内网其他主机、端口
设备(10.6.0.1) → GRE隧道 → 阿里云(解封装) → 目标:本机(80) → localhost:8080
                                                             
# 将 GRE 隧道来的特定流量重定向到本机服务
# 一、DNAT
# 将到B公网IP 80端口的流量转发到本机的8080端口
iptables -t nat -A PREROUTING -d <B的公网IP> -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8080
# 二、REDIRECT (基于源IP网段)
iptables -t nat -A PREROUTING -s 10.6.0.0/22 -p tcp --dport 80 -j REDIRECT --to-port 8080通过 DNAT、REDIRECT 实现主机、端口转发。
(1)场景一:本地端口映射(两者都可用)
# REDIRECT方式(简单)
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
# DNAT方式(等效)
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8080REDIRECT 可以看作是 DNAT 的特例:REDIRECT --to-port 8080 等价于 DNAT --to-destination 127.0.0.1:8080
(2)场景二:跨主机端口转发(只能用DNAT)
# 将本机80端口转发到另一台服务器的8080端口
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080
# 还需要允许转发
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT
DNAT 修改了目标 IP,需要搭配 SNAT 才能确保回复路径与请求路径一致。
未验证,出问题请问 AI。
# SNAT:修改源地址(关键!)
iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.100 --dport 8080 -j SNAT --to-source [你的服务器IP](3)负载均衡(只能用DNAT)
# 将流量分发到多个后端服务器
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100-192.168.1.102:8080同样修改了目标 IP,需要搭配 SNAT。
3、转发到公网
# 架构拓扑表述:
设备(10.6.0.1) → GRE隧道 → 阿里云(解封装) → 目标:百度IP → 公网接口(eth0) → 互联网
                                                      ↑
                                              需要跨接口转发
# 数据包路径: 
[从tun1进入的数据包]
    ↓
PREROUTING链 (nat)
    ↓
路由决策 (目标不是本机,需要转发)
    ↓
FORWARD链 (filter)
    ↓
POSTROUTING链 (nat) ← 这里执行MASQUERADE
    ↓
[从eth0发出到公网]
# 确保 FORWARD 链允许: iptables -L INPUT
# 如果默认策略是放行: Chain INPUT (policy ACCEPT) 且没有 DROP、REJECT 则不需要配置
# 反之,需要配置: 允许从GRE发出、发往GRE的转发流量
iptables -A FORWARD -s 172.16.0.0/30 -j ACCEPT
iptables -A FORWARD -d 172.16.0.0/30 -j ACCEPT
基本知识:
源IP: 172.16.0.1 (A服务器的内网IP、对等 IP)
目标IP: 183.2.172.177 (百度IP)不用 SNAT/MASQUERADE,目标通过 eth0 发出到公网,不能访问的原因:
- 源 IP 不可路由(回包找不到) 
- 云服务商安全策略:丢弃源 IP 不是公网 IP 的数据包 
(1)云环境无需配置
实测阿里云会自动 SNAT,完全不需要配置。GRE 配好之后,直接可以访问公网。京东云则需要配置。
你的服务器: 172.18.141.168 → 云网关 → SNAT → 公网IP → 目标服务器: 52.220.34.227
目标服务器回复: 52.220.34.227 → 云网关 → DNAT → 你的服务器: 172.18.141.168(2)静态公网 IP(SNAT)
云服务商都是做了 DNAT 的,不能直接用公网 IP(和 ip tunnel add .. remote .. 一样必须用内网 IP),具体分析看后面的 conntrack 部分。
# 将 GRE 隧道流量 SNAT 为 B 的内网 IP
iptables -t nat -A POSTROUTING -s 172.16.0.0/30 -j SNAT --to-source <B的内网IP>
# 正常机房转发的配置。云服务器场景不适用。
# iptables -t nat -A POSTROUTING -s 172.16.0.0/30 -j SNAT --to-source <B公网IP>
(3)动态 IP(MASQUERADE)
MASQUERADE 在这种动态路由和隧道场景中通常比 SNAT 更可靠,特别是当涉及连接跟踪和状态管理时。
# 来源是 gre 对端 ip
iptables -t nat -A POSTROUTING -s 172.16.0.0/30 -j MASQUERADE
# 标记来自 tun1 接口的流量
iptables -t mangle -A PREROUTING -i tun1 -j MARK --set-mark 0x1
iptables -t nat -A POSTROUTING -m mark --mark 0x1 -j MASQUERADE
(4)优劣势对比
MASQUERADE 可以看作是 SNAT 的"智能简化版":
✅ 在复杂/动态环境中更可靠
✅ 配置更简单
✅ 适应性更强
❌ 性能稍差
❌ 控制精度较低(SNAT 可以根据不同的 源 IP 指定转发到不同的公网 IP 上)
三、抓包验证
1、tcpdump
在服务器 B 上使用 tcpdump(网络抓包分析工具)来抓取隧道数据(跟踪数据包路径)。
# -i: interface, 指定监听某个网络接口
# -c: count, 捕获 count 个数据包后自动退出
# -n: 不进行域名解析,直接显示IP地址
# host www.baidu.com: 过滤条件,只捕获与百度服务器相关的流量
tcpdump -i tun1 -n
tcpdump -i tun1 -n host www.baidu.com -c 5
# 同步监控网卡输出
tcpdump -i eth0 -n host www.baidu.com -c 20
# icmp 指定抓 icmp 协议
tcpdump -i tun1 -n icmp
# 'ip proto 47' 抓 gre 协议
tcpdump -i eth0 -n 'ip proto 47'
# icmp、发送、接收方包含 10.6.0.1
tcpdump -i eth0 -n icmp and host 10.6.0.1
# 组合条件
tcpdump -i eth0 -n "icmp and (host 172.18.141.169 or host 10.6.0.1)"
dump 数据示例:
# 一、ping baidu
# 172.16.0.1: GRE 拆包后 A 的 IP
# 183.2.172.17: 百度 IP
# (172.16.0.1 > 183.2.172.17)发送 ICMP echo request
# (183.2.172.17 > 172.16.0.1)响应 ICMP echo reply
root@1761270860171:~# tcpdump -i tun1 -n -c 10
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun1, link-type LINUX_SLL (Linux cooked v1), snapshot length 262144 bytes
02:14:49.840062 IP 172.16.0.1 > 183.2.172.17: ICMP echo request, id 5908, seq 12, length 64
02:14:49.844735 IP 183.2.172.17 > 172.16.0.1: ICMP echo reply, id 5908, seq 12, length 64
02:14:50.841385 IP 172.16.0.1 > 183.2.172.17: ICMP echo request, id 5908, seq 13, length 64
02:14:50.846042 IP 183.2.172.17 > 172.16.0.1: ICMP echo reply, id 5908, seq 13, length 64
# 二、curl baidu
# 可见,正常的数据包发送是有发有收的,172.16.0.1 即使发送方也是接收方
root@1761270860171:~# tcpdump -i tun1 -n -c 10
02:21:00.593764 IP 172.16.0.1.60798 > 183.2.172.177.80: Flags [S], seq 3634248704, win 27200, options [mss 1360,sackOK,TS val 2947708680 ecr 0,nop,wscale 7], length 0
02:21:00.598136 IP 183.2.172.177.80 > 172.16.0.1.60798: Flags [S.], seq 3554189190, ack 3634248705, win 8192, options [mss 1360,sackOK,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,wscale 5], length 02、conntrack
在B服务器检查连接跟踪
# 清空B服务器的连接跟踪表
conntrack -D
# 在A服务器测试
curl --interface tun1 --connect-timeout 3 http://www.baidu.com/
# 立即在B服务器查询连接跟踪(查找已有记录然后立即输出,类似 cat | grep)
conntrack -L | grep 183.2.172.177
# B服务器持续跟踪(类似 tail -f)
conntrack -E
conntrack 数据示例:
// SNAT 异常连接
tcp 6 109 SYN_SENT src=172.16.0.1 dst=183.2.172.177 sport=33906 dport=80 [UNREPLIED] src=183.2.172.177 dst=114.67.200.161 sport=80 dport=33906 mark=0
// MASQUERADE 正常连接
tcp 6 118 TIME_WAIT src=172.16.0.1 dst=183.2.172.177 sport=42824 dport=80 src=183.2.172.177 dst=172.30.0.7 sport=80 dport=42824 [ASSURED] mark=0可见,MASQUERADE 正常连接回程数据包 dst 是 B 服务器的内网 IP 172.30.0.7,而不是公网 IP 114.67.200.161。说明云服务商在底层做了 DNAT 转换。
实测 SNAT --to-source 使用 B 的内网 IP 可以正常连接。
验证 B 服务器的默认源头 IP (是否 DNAT)
# 查看B服务器出站时的默认源IP
tcpdump -i eth0 -n -c 3 'tcp and port 80' &
# 在B服务器本地测试
curl http://httpbin.org/ip
# 输出 17:29:02.101427 IP 172.30.0.7.58822 > 100.65.1.10.80: Flags [.], ack 1, win 507, length 0
# 可见,源 IP 是 172.30.0.7(B 内网 IP)3、netcat
使用 netcat 起一个 8080 端口的服务,用于 curl 测试。
while true; do 
  echo -e 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello' | nc -l -p 8080
done
while true; do echo -e 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello' | nc -l -p 8080 -q 1; done
四、(C -> B) gre A
服务器 B 和 C 属于同一个内网网段,C 的 IP 为 172.30.0.8。C 需要发送数据包到 A 的内网机器上。可以预想到的方案是数据包先经过 B,然后通过 A/B 的 GRE 隧道发送给 A。
1、配置 VPC 路由(推荐)
C 配置路由下一跳为服务器 B 的 IP 172.30.0.7:
# 172.18.96.0/20 是服务器 A 的内网网段
ip route add 172.18.96.0/20 via 172.30.0.7 dev eth0为什么要添加 VPC 路由?
原因是:跨 ECS 转发数据包,阿里云交换机会检查目标 IP 的路由,目标为非 VPC 网段的包会被丢弃。数据包路径分析:
机器 C → 发送到172.30.0.7 → 阿里云虚拟交换机 → ❌ VPC路由表没有172.18.96.0/20路由 → 丢弃阿里云添加自定义路由:172.18.96.0/20 跳到 ECS 172.30.0.7。
2、通过映射网段访问
详见本站另一篇文章 —— 内网 IP 映射(GRE 终端互访)。
五、扩展
1、rp_filter
如果转发不生效,可以试试调整内核参数 rp_filter。
内核接受到数据包之后不是第一时间发出去,而是考虑回程路由是否可达。
- 0:不进行反向路径检查 
- 1:严格检查,只接受【接收接口,比如 tun1】可达的数据包 
- 2:松散模式(一般默认),接受任何路由可达源 IP 的数据包 
# 1、检查相关内核参数
echo "net.ipv4.ip_forward = $(cat /proc/sys/net/ipv4/ip_forward)"
echo "net.ipv4.conf.all.rp_filter = $(cat /proc/sys/net/ipv4/conf/all/rp_filter)"
echo "net.ipv4.conf.tun1.rp_filter = $(cat /proc/sys/net/ipv4/conf/tun1/rp_filter)"
# 2、修复方案: 
# (1)临时调整测试
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/eth0/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/tun1/rp_filter
# (2)编辑sysctl配置
echo "net.ipv4.conf.all.rp_filter = 0" >> /etc/sysctl.conf
echo "net.ipv4.conf.default.rp_filter = 0" >> /etc/sysctl.conf
echo "net.ipv4.conf.eth0.rp_filter = 0" >> /etc/sysctl.conf
echo "net.ipv4.conf.tun1.rp_filter = 0" >> /etc/sysctl.conf
# 立即生效
sysctl -p