linux snat mac 桥,关于SNAT在bridge中不生效的问题

本周在协助验证一套虚拟网络的方案,该方案包含一个bridge,向上对接容器的veth,并接管真实NIC作为tx口,方案中需要在bridge中做SNAT,具体hook点位于POST_ROUTING,命令如下:iptables -t nat -A POSTROUTING -d 192.168.0.0/24 -j SNAT --to-source 192.168.0.5

为了验证该方案,我创建了一对veth,其中一端划分到独立的netns中,命令如下:ip link add br-veth type veth peer name veth

ip link set br-veth up

ip link set veth up

brctl addif br0 br-veth

ip netns add test-zone

ip link set veth netns test-zone

ip netns exec test-zone ip addr add 192.168.0.100/24 dev veth

为了避免方案过于复杂,规避gw带来的影响,这里假设对端和本端在同一子网,在test-zone中ping对端,我发现对端抓包看到的源IP并未变为192.168.0.5,换句话说SNAT未生效。

在veth的tx方向,这里仅需做二层转发即可,阅读bridge的源码可以发现如下路径(kernel 3.10/4.9无显著区别):br_forward -> BR_FORWARD -> br_nf_forward -> BR_POST_FORWARD -> br_nf_post_routing -> INET_POST_ROUTING

然而挂载POST_ROUTING的SNAT竟然未生效,真的令人匪夷所思。一番google之后,发现需要enable一个标志:echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables

纵观bridge实现,该sysctl的值最终设置到了变量nf_call_iptables,而该变量仅在br_nf_pre_routing中使用。显然,这是不符合预期的。

惯性思维使然,总认为既然有代表特性是否开启的变量,那么在代码相关的分支处一定会用到该变量,而bridge的实现则正好不是这样。

可以看到,只有开启了nf_call_iptables,才会进入网络层的netfilter hook点:static unsigned int br_nf_pre_routing(void *priv,

struct sk_buff *skb,

const struct nf_hook_state *state)

{

struct nf_bridge_info *nf_bridge;

struct net_bridge_port *p;

struct net_bridge *br;

__u32 len = nf_bridge_encap_header_len(skb);

if (unlikely(!pskb_may_pull(skb, len)))

return NF_DROP;

p = br_port_get_rcu(state->in);

if (p == NULL)

return NF_DROP;

br = p->br;

...

if (!brnf_call_iptables && !br->nf_call_iptables)

return NF_ACCEPT;

...

nf_bridge_put(skb->nf_bridge);

if (!nf_bridge_alloc(skb)) // 注意这里!

return NF_DROP;

...

NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, state->net, state->sk, skb,

skb->dev, NULL, br_nf_pre_routing_finish);

return NF_STOLEN;

}

很容易忽略标着注释的那一行,而恰是那一行,直接决定了是否进入NF_INET_FORWARD和NF_INET_POST_ROUTING的HOOK点。static unsigned int br_nf_forward_ip(void *priv,

struct sk_buff *skb,

const struct nf_hook_state *state)

{

struct nf_bridge_info *nf_bridge;

struct net_device *parent;

u_int8_t pf;

if (!skb->nf_bridge) // 看这里!

return NF_ACCEPT;

...

NF_HOOK(pf, NF_INET_FORWARD, state->net, NULL, skb,

brnf_get_logical_dev(skb, state->in),

parent, br_nf_forward_finish);

return NF_STOLEN;

}static unsigned int br_nf_post_routing(void *priv,

struct sk_buff *skb,

const struct nf_hook_state *state)

{

struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);

struct net_device *realoutdev = bridge_parent(skb->dev);

u_int8_t pf;

if (!nf_bridge || !nf_bridge->physoutdev) // 看这里!

return NF_ACCEPT;

...

NF_HOOK(pf, NF_INET_POST_ROUTING, state->net, state->sk, skb,

NULL, realoutdev,

br_nf_dev_queue_xmit);

return NF_STOLEN;

}

参加工作后,见识了很多相当“野”的解决方案,一言不合就得改kernel,为了避免遇到问题时一脸懵逼,平时还是要多花时间熟悉内部实现!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值