Administrator
发布于 2025-10-26 / 8 阅读
0
0

GRE 转发

在 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 default

2.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:8080

REDIRECT 可以看作是 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 0

2、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


评论