开头
今天尝试使用veth+bridge在Linux模拟交换机的情况(不配置net namespace),本以为环境应该很快搞定的,却实实在在的花费了一整天
过程
ip link add testbridge type bridge
ifconfig testbridge up
ip link add dev veth1 type veth peer name veth1_p
ip link add dev veth2 type veth peer name veth2_pip addr add 192.168.254.1/24 dev veth1
ip addr add 192.168.254.2/24 dev veth2ip link set veth1 up
ip link set veth2 up
ip link set veth1_p up
ip link set veth2_p upbrctl addif testbridge veth1_p
brctl addif testbridge veth2_p# brctl showmacs testbridge
相应的命令如上,要知道,veth的工作模式,就是从一端xmit出去之后,会强制从peer接受(peer走netif_rx接受),在上述命令完成之后,按理执行 curl --interface veth1 http://192.168.254.2:2333
,表示从 veth1出去,访问 veth2上的服务,即 veth1->veth1_p->testbridge->veth2_p->veth2
,按理命令应该返回RESET给我(实际没启动2333服务),但是实际上直接卡主。
通过 tcpdump -i testbridge 以及 tcpdump -i veth2 发现,ARP请求顺利到达了,但是没回复ARP响应,通过arp_process
代码跟踪发现出现问题的点在这里
static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
{....if (arp->ar_op == htons(ARPOP_REQUEST) &&ip_route_input_noref(skb, tip, sip, 0, dev) == 0) {...}....
}
请求为ARP_REQUEST(满足),并且需要 sip (192.168.254.1)到tip(192.168.254.2) 在本机有路由,ip_route_input_noref
实际调用了ip_route_input_slow
,盲猜又和反向路由检测有关系,果然通过cat /proc/net/stat/rt_cache
发现,in_martian_src
计数是在不断上升的,于是关闭反向路由检测:
echo "0" > /proc/sys/net/ipv4/conf/veth2/rp_filter
可惜,值还在不断上升,查看反向路由检测的实现
int fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst,u8 tos, int oif, struct net_device *dev,struct in_device *idev, u32 *itag)
{int r = secpath_exists(skb) ? 0 : IN_DEV_RPFILTER(idev);struct net *net = dev_net(dev);// r 为 0表示没开反向路由检测// oif 入参就是固定0,所以不然会进判断// ACCEPT_LOCAL: 允许接受源为同host的IP地址// inet_lookup_ifaddr_rcu 会找源IP 192.168.254.1 是不是本机接口IP,是的话返回非NULL,显然是的if (!r && !fib_num_tclassid_users(net) &&(dev->ifindex != oif || !IN_DEV_TX_REDIRECTS(idev))) {if (IN_DEV_ACCEPT_LOCAL(idev))goto ok;/* with custom local routes in place, checking local addresses* only will be too optimistic, with custom rules, checking* local addresses only can be too strict, e.g. due to vrf*/if (net->ipv4.fib_has_custom_local_routes ||fib4_has_custom_rules(net))goto full_check;/* Within the same container, it is regarded as a martian source,* and the same host but different containers are not.*/if (inet_lookup_ifaddr_rcu(net, src))return -EINVAL;ok:*itag = 0;return 0;}full_check:return __fib_validate_source(skb, src, dst, tos, oif, dev, r, idev, itag);
}
可以看到,核心是因为 fib_validate_source 会判断 inet_lookup_ifaddr_rcu 导致,好在 有一个总开关 IN_DEV_ACCEPT_LOCAL
可以控制。
echo "1" > /proc/sys/net/ipv4/conf/veth2/accept_local
执行完成之后,果然,curl --interface veth1 http://192.168.254.2:2333
成功返回了RESET,反过来,curl --interface veth2 http://192.168.254.1:2333
依旧会卡主,因为我们需要同样的针对veth1做同样的设置
echo "0" > /proc/sys/net/ipv4/conf/veth2/rp_filter
echo "1" > /proc/sys/net/ipv4/conf/veth2/accept_local
注意,如果机器安装了Docker,会导致FORWARD默认为DROP,导致bridge无法转发请求
# iptables -S
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
需要设置 iptables -I FORWARD -i testbridge -j ACCEPT
结尾
回到开头的问题,如果有namespace的情况下,即网上常见的将veth1和veth2分别加入到某个ns中来模拟docker,这种情况下是无需设置accept_local之类的,原因是 fib_validate_source->inet_lookup_ifaddr_rcu
函数,会在 当前 dst ip 下的 namespace查询,是否存在请求的src ip 对应的设备,自然,在容器的环境下,src ip和dst ip对应的网络设备均是在各自namespace下的