近日温州皮鞋厂老板正在忙着学习Linux Bridge以及诸多虚拟网卡相关的东西,老湿给了一些指导,但最根本的还要靠温州老板自己。就好像有仙灵在聆听心声,我正因为温州老板的缘故一而再再而三地怀念曾经玩转Linux Bridge,Linux Netfilter的那段痛并快乐着的时光,另外一个好玩的东西恰在此时切入。
大概有三年多没有玩Linux Bridge了,甚是想念。感谢同事给我一个Bridge方面的疑难杂症让我诊断!#define BRNF_BRIDGED_IP_DNAT 0x40
然后在探测到发生了IP DNAT的时候置位,修改br_nf_pre_routing_finish:
if (dnat_took_place(skb)) {
nf_bridge->mask |= BRNF_BRIDGED_IP_DNAT;
...
}
最后在判断是否可以在该Port转发时判断IP DNAT这个新增mask,修改should_deliver:
static inline int should_deliver(const struct net_bridge_port *p,
const struct sk_buff *skb)
{
struct nf_bridge_info *nf_bridge = skb->nf_bridge;
return (((p->flags & BR_HAIRPIN_MODE) || skb->dev != p->dev || nf_bridge->mask & BRNF_BRIDGED_IP_DNAT) &&
br_allowed_egress(p->br, nbp_get_vlan_info(p), skb) &&
p->state == BR_STATE_FORWARDING);
}
然后就通了。
if ((err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))) {
改为:
if ((err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, bridge_parent(dev)?bridge_parent(dev):dev))) {
这样就可以保证,如果第一次进入br_nf_pre_routing_finish之后发现IP DNAT已经发生并且将skb的dev改为了物理网卡,那么通过br_nf_hook_thresh第二次进入br_nf_pre_routing_finish在调用ip_route_input时,仍然使用Bridge作为dev参数(虽然此时skb的dev已经是物理网卡了),这样就可以保证查到正确的路由,接下来的流程自然会进入”将此数据包路由到本地IP层“这个逻辑了。
nf_bridge->in_prerouting = 0;
if (br_nf_ipv4_daddr_was_changed(skb, nf_bridge)) {
if (bridge_parent(dev) != NULL)
goto go;
...
go:
rt = bridge_parent_rtable(nf_bridge->physindev);
if (!rt) {
kfree_skb(skb);
return 0;
}
skb_dst_set_noref(skb, &rt->dst);
}
...
br_nf_hook_thresh(NF_BR_PRE_ROUTING, net, sk, skb, skb->dev, NULL,
br_handle_frame_finish);
return 0;
}
这个修改过后,虽然经过了IP DNAT,但是Bridge依然没有放弃数据包的所有权,依然在尝试在二层路由数据包,那么后果可想而知,依然会面临Hairpin的问题(详见3.10版本内核的分析),解决之道当然还是用brctl或者sysfs来设置br0的eth1接口的Hairpin了。
有人有疑问,这个问题非常简单啊,为什么不把net.bridge.bridge-nf-call-iptables这玩意儿关掉呢?反正DNAT之前的目标IP也是Box0,下一跳解析肯定会把帧从Box0的Bridge送入该Box0的IP层的,干嘛要费事这么折腾呢?其实如果仅仅为了解题,那关掉net.bridge.bridge-nf-call-iptables确实是最简单的办法,然而事情并非那么简单。
我上面说了,要是用OVS的话,所有问题都是简单的问题,两个NAT就是几条流表的事,而OVS本身就是一个增强版的Bridge,其实OVS是将Bridge,BR Netfilter,IP Netfilter甚至IP Route等结合在一起了,他本身就是折腾SDN,折腾NFV的利器,然而现实中,系统并不是你的,并不是你想用OVS就用的,更何况,系统中可能运行着其它的逻辑依赖Bridge,Netfilter等,所以说有时候你必须用Bridge,BR Netfilter,IP Netfilter,IP Route等组合解决所有问题。而我恰恰是这方面的专家,哈哈。
这么说吧,现实中本文的这个问题就是不能用OVS。细节不便多说,kube-proxy玩过吗?没玩过也没关系,我也没怎么玩过,不管怎样,这玩意儿就需要iptables,你想用OVS绕过一切?好办,先适配一个kube-proxy再说吧。只截图一幅,便能说明问题:
详情参阅:https://kubernetes.io/docs/concepts/cluster-administration/network-plugins/
不管是3.10的内核还是4.9的内核,虽然问题不同,但是解决问题的思路是一致的,无外乎两种方案,其一是还按照原来的Bridge路径发送IP DNAT后的帧,其二是将IP DNAT后的帧路由到本地IP层。在本文中,可以掌握Bridge的很多知识,包括Hairpin,IP DNAT等。然而本文并没有涉及关于STP这个非常重要的问题。
剩下的,明早再说吧
---以下次日添加:
在本文描述4.9内核解法的解法一里,我提到了broute配置,我估计你不一定了解ebtables的broute和redirect的区别,这里其实又是一个坑,详情请见《ebtables之BROUTING和PREROUTING的redirect的区别》,总之,Linux Bridge的坑实在是太多了,这并非Bug,而实在是仁者见仁智者见智的事情,作者们怎么认为就是怎么实现,毕竟这也是一块没有标准的领域,有谁见过哪个交换机可以配置IP DNAT并且规定如何实施的规范吗?没有这样的规范。Cisco业界领航,它的交换机,路由器里支持什么,那这个东西基本就是标准,至少是它家的标准,影响着整个产业,而且它家的东西又不让你内窥内幕...Linux与之相反,谁都可以看个究竟,但这并不意味着谁都有话语权,Linux开源社区也是个熟人社会,被几家独大的巨头把持的,比如Google要是提交一个Patch,十有八九会被Apply,同样的东西被一个无业游民提交估计200%被Deny...话说Android的定制那么琐碎,不也向主线”贡献“了很多垃圾么?就因为它是Google的。
最后还是描述一下我自己,我再次强调,我编程编的不好,但也不是一点也不会,我稍微会一点,但是我对网络协议栈处理的数据流路径非常熟悉,对网络协议非常熟悉,所以可以快速定位一些网络问题并知道如何快速修正。
另外想说的是,Linux内核从2.4.1到4.10,核心的东西可以说根本就没有变化,目标永远都是一致的,所以不要拿O(n)调度器和CFS的重大差异来说事儿,想彻底了解一个技术,第一步肯定要用起来,然后是熟悉它的工作原理,最后才是看源代码,温州皮鞋厂老板总以为学一项技术看下源码就OK了,这是极其不对的。如果有人给你个发送机,你都不知道这个东西能干嘛,你都没让它转起来,直接就给拆了,这是典型的为了拆解而拆解,就算你看懂了里面有汽缸,有活塞...你也根本猜不出这玩意儿是干嘛用的,更想象不到它动起来的样子。如今分析源码的人太多了,各种简历上写上熟读过XXX源代码,精通YYY源码...就好像特别牛逼,真的牛逼吗?问起来除了能背出几个函数的名字显得很牛逼之外,很多人(我并非说全部)其实根本连它的原理都不懂。Linux内核代码写得还算不错,也比较好读,花点时间读完它也不是不可能的,至少能读完几个子系统的吧,但是更好的做法难道不是先学习操作系统原理或者网络协议吗?
还是扯到我自己。刚毕业那年去参加了吉大的校园招聘会,应聘了中软吉大(年代久远,不再匿名公司,希望能留下足迹)的程序员职位,参加了集体笔试,大概十个人左右的样子,成绩得了第一名。后来在我们所有入职的新员工培训期结束的时候,各位老员工开始一对一带我们进入第一份工作,带我的人是一位比较资深的工程师,也是个领导,这里面可能有我笔试成绩好的原因。在跟他第一次聊的时候,我说我编程编的不好,我怕自己会做不好。他告诉我,编码和对网络协议的理解,你觉得哪个重要呢?我想他心里已经有了答案,只是反问我一下,打消我的自卑罢了...当时脑海中顿时出现了在招聘会上如何获得面试官好感的镜头,我的简历上除了写了Java会一点但不精通之外,其它的全部都是网络方面的:ATM,X.25,帧中继,IPSec,GRE,OSPF,PPP,DDN,交换网络(如今很多都被淘汰了)...然后就获准参加笔试了,可能是因为这份简历比较独特吧。好在笔试题目中编程题不限语言,我还有个Java可以抵挡,其它的题就是奋笔疾书了。再往前回溯,我在想是什么时候学了那么多网络技术的啊。那是在河南农业大学的自习室里看书,在郑州工学院实验室里验证就这么折腾了两年玩会的,后来又参加了华为HCSE的考试并拿了证书,当时想考CCNP或者IE的,可是没那么多钱就算了...那年是2004年,再往前追溯,就说到了首次接触Java的时候,那时我还在哈尔滨,有个周末在席殊书屋闲逛,不知道怎么搞的买了一本Java书,也看不懂,好像现在还留着。不过书在上海,没带来深圳,不然我肯定会拍照贴图的,那年是2002年。
社区已经在今年早些时候修正了这个问题,也承认了这是个问题(但并非BUG??),详见下面的patch:
https://www.spinics.net/lists/stable/msg156257.html
然而,从这个patch的说明可以看到,他们的问题描述还是不准确:
Problem:
br_nf_pre_routing_finish() calls itself instead of
br_nf_pre_routing_finish_bridge(). Due to this bug reverse path filter drops
packets that go through bridge interface.
User impact:
Local docker containers with bridge network can not communicate with each
other.
难道仅仅是reverse path和docker吗??这进一步印证了这个熟人社会已经...唉