流量转发实战
遇到一个有趣的网络转发课题,整理出来分享
实战场景
- 如上图所示,FW/NAT 服务器上有四张网卡
- eth0 互联内网交换机,网段10.222.0.1/16,有4个设备需要访问外网
- eth1 互联电信网络,2个出口地址 6.6.1.5/24,6.6.1.6/24;(网关 6.6.1.1 主路由)
- eth2 互联联通网段,1个出口地址 6.6.2.5/24 (网关 6.6.2.1)
- eth3 互联移动网段,1个出口地址 6.6.3.5/24 (网关 6.6.3.1)
- 需求是定制一套可配置的流量调度器,实现
- 10.222.0.11 网络流量出口 eth1-6.6.1.5
- 10.222.1.11 网络流量出口 eth1-6.6.1.6
- 10.222.2.11 网络流量出口 eth2-6.6.2.5
- 10.222.3.11 网络流量出口 eth3-6.6.3.5
- 要求配置独立 (即访问相同的ip地址,不同的内外设备出口ip不同),也同时可低成本动态修改 (提供api等方式)
解决思路
- 涉及到网络出口转发,那么就得用到snat,而snat最低成本的方式莫过于netfilter体系下的 nat
- 多网卡多网关,而且要求根据来源设备来区分网络出口,那么必然需要 路由表+策略路由,通过来源IP来定义流量转发所匹配的路由规则
解决方案
综上分析,借助 nat + 路由表+策略路由,有以下2个可行的解决方案
- 动态更新策略路由表+nat表
- 固化策略路由表 + 动态更新nat表
1. 动态更新策略路由表+nat表
1.1 除了主路由(ctcc), 为另外2个互联网卡创建独立路由表 (1002/1003)
# eth2
/sbin/ip route flush table 1002
/sbin/ip route add default via 6.6.2.1 dev eth2 table 1002
# eth3
/sbin/ip route flush table 1003
/sbin/ip route add default via 6.6.3.1 dev eth3 table 1003
1.2 定义策略路由
# 10.222.2.11
/sbin/ip rule add from 10.222.2.11 lookup 1002
# 10.222.3.11
/sbin/ip rule add from 10.222.3.11 lookup 1003
由于eth1 为主路由,所以无需为 10.222.0.11, 10.222.1.11 额外定义策略
1.3 开启forward
sysctl -w net.ipv4.ip_forward=1
也 可考虑 如下方法固化修改
cat > /etc/sysctl.d/999-custom.conf <<EOF
net.ipv4.ip_forward=1
net.ipv4.tcp_syncookies=1
net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.all.rp_filter=0
net.ipv4.tcp_max_syn_backlog=65535
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.tcp_tw_reuse=0
net.ipv4.tcp_tw_recycle=0
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
# net.netfilter
net.netfilter.nf_conntrack_max=1048576
net.netfilter.nf_conntrack_buckets=262144
net.netfilter.nf_conntrack_tcp_loose=0
EOF
1.4 生成nat规则 (注意多出口ip需要额外定义)
iptables -t nat -A POSTROUTING -s 10.222.0.11 -o eth0 -j SNAT --to 6.6.1.5
iptables -t nat -A POSTROUTING -s 10.222.1.11 -o eth0 -j SNAT --to 6.6.1.6
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
iptables -t nat -A POSTROUTING -o eth2 -j MASQUERADE
1.5 动态调度
- 指定网卡出口 需要更新策略路由 (
ip rule delete/add
) - 指定网卡附加IP 出口,需要更新 iptables 防火墙 (
iptables -D
)
2. 固化策略路由表 + 动态更新nat表
2.1 除了主路由(ctcc), 为另外2个互联网卡创建独立路由表 (1002/1003)
# eth2
/sbin/ip route flush table 1002
/sbin/ip route add default via 6.6.2.1 dev eth2 table 1002
# eth3
/sbin/ip route flush table 1003
/sbin/ip route add default via 6.6.3.1 dev eth3 table 1003
2.2 定义策略路由 (打标签)
# 非主路由的网卡有多少就定义多少个标签
# 0x00001002-eth2
/sbin/ip rule add fwmark 0x00001002 table 1002
# 0x00001003-eth3
/sbin/ip rule add from 0x00001003 lookup 1003
2.3 开启forward
sysctl -w net.ipv4.ip_forward=1
也 可考虑 如下方法固化修改
cat > /etc/sysctl.d/999-custom.conf <<EOF
net.ipv4.ip_forward=1
net.ipv4.tcp_syncookies=1
net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.all.rp_filter=0
net.ipv4.tcp_max_syn_backlog=65535
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.tcp_tw_reuse=0
net.ipv4.tcp_tw_recycle=0
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
# net.netfilter
net.netfilter.nf_conntrack_max=1048576
net.netfilter.nf_conntrack_buckets=262144
net.netfilter.nf_conntrack_tcp_loose=0
EOF
2.4 生成nat规则 (使用nftables)
- 针对需要从特定网卡出去的流量添加meta mark标签,策略路由只需识别固定的标签即可选择正确的路由表
- 针对特定的snat出口的地址进行map数据结构管理
table ip nat {
set outbond_eth2 {
type ipv4_addr
flags interval
elements = { 10.222.2.11 }
}
set outbond_eth3 {
type ipv4_addr
flags interval
elements = { 10.222.3.11 }
}
map outbond_ip {
type ipv4_addr : ipv4_addr
flags interval
elements = { 10.222.0.11 : 6.6.1.5, 10.222.1.11 : 6.6.1.6 }
}
chain nat_prerouting {
type nat hook prerouting priority 100; policy accept;
ip saddr @outbond_eth2 meta mark set 0x00001002 ct mark set 0x00001002
ip saddr @outbond_eth3 meta mark set 0x00001003 ct mark set 0x00001003
}
chain filter_prerouting {
type filter hook prerouting priority filter; policy accept;
ct status snat ct direction original ct mark != 0x00000000 meta mark set ct mark
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname { "eth1", "eth2", "eth3" } snat to ip saddr map @outbond_ip
oifname { "eth1", "eth2", "eth3" } masquerade
}
}
2.5 动态调度
-
指定网卡出口 , 需修改出口set数据 (
outbond_eth2
,outbond_eth3
)- 查询
nft list set ip nat outbond_eth2
- 增加
nft add element ip nat outbond_eth2 \{ 10.222.2.12 \}
- 删除
nft delete element ip nat outbond_eth2 \{ 10.222.2.12 \}
- 查询
-
指定网卡附加IP 出口,需修改出口map数据 (
outbond_ip
)- 查询
nft list map ip nat outbond_ip
- 增加
nft add element ip nat outbond_ip \{ 10.222.1.12: 6.6.1.6 \}
- 删除
nft delete element ip nat outbond_ip \{ 10.222.1.12: 6.6.1.6 \}
- 查询
方案分析
- 方案1需要动态修改策略路由表,而策略路由表是按顺序自上而下(1-32767)遍历匹配,随着调度设备的增多,策略路由查询会出现明显的cpu消耗
- 方案2借助了 nftables的 set、map数据结构来打标签、指定snat,可拓展性要更高,同时CPU消耗低,性能也更好。
扩展思考
另外一个思路是基于nftables的 flowtables
,可以提高转发性能。但随着ebpf的兴起,针对当前的场景可直接使用 ebpf 绕过系统内核
- 定义转发表
- eth0 10.222.0.11 =SNAT= eth1 6.6.1.5
- eth0 10.222.1.11 =SNAT= eth1 6.6.2.5
- eth0 10.222.2.11 =SNAT= eth2 6.6.2.5
- eth0 10.222.3.11 =SNAT= eth3 6.6.3.5
- eth1 10.222.0.0/16 =FW= eth0
- eth2 10.222.0.0/16 =FW= eth0
- eth3 10.222.0.0/16 =FW= eth0
- 监听 eth0 的数据包
- 如果命中规则,则进行snat转换,投递到对应的网络出口 (记录状态,实现端口分配与回收)
- 监听 eth1/eth2/eth3 的数据包
- 如果命中规则,则直接转发到 eth0网口
ebpf的难点在于实现类似conntrack的连接记录(可简单暴力超时回收例如udp;也可以往复杂里做按传输层的连接状态,例如tcp)
引申场景
如果要实现
- 内网的客户端 (
10.222.0.11
) 访问8.8.8.8
走 eth2(cucc) - 内网的客户端 (
10.222.0.12
) 访问8.8.8.8
走 eth3(ctcc) - 内网的客户端 (
10.222.1.11
) 访问8.8.8.8
走 eth1(ctcc) 6.6.2.6
要如何配置?