首页 > 代码库 > 一个关于Linux Bridge配置的吐嘈

一个关于Linux Bridge配置的吐嘈

话说有些事情十分适合在放假前的一天折腾一天,但绝对不适合在你准备去吃饭前多看一眼...我上周就碰到了这么一件揪心的事,最终以低血糖收场,十分狼狈地四处觅食,却觅到了一包超级辣的鸡爪,吃完后感觉症状加重了,于是向邻座的同事乞讨了巧克力糖,唉...这一切起源于我在准备吃午饭前一头扎进了一个技术问题,本以为能靠配置搞定,最终却还是不得不以修改The Fxxxing code告终!事情起源于公司的一个禁令:禁止上外网!
      禁止工作时间上外网,碰到问题就只能拖着,多么奇葩的行政管理策略。事实上,想在工作期间上外网一般有三个办法:
1.如果看直播之类的,可以躲在厕所里蹲着看,没人知道你在看什么,代价就是3G流量和麻醉状态的腿和脚;
2.填一张签报单,说明自己上网的理由,比如查资料之类很笼统但却没有漏洞的理由,然后找领导签字,一般都会批准的,然后你就可以在查资料之余顺便看看新闻,天气预报之类的;
3.原自公司网络管理的漏洞,仅仅封掉了熟知端口,有些端口却开着,如果你在家里开一台Linux机器,那么就可以通过家里的机器上网了。
以上3点中,第1点基本不可能了,因为自公司搬家以后,厕所就一点信号都没有了,别说上网,万一没带纸都是很麻烦的事,第二点大家都会做,也确实这么做了,结果公司禁令的最大效果就是各种打印,各种签字,各种跑,但是技术人员不满足于这种非技术的解决方式,于是一定要尝试一下第3种方式。
      一般而言,家庭网络的拓扑都是在最外面有一个无线路由器,然后里面几个设备,PC,各种PAD,手机之类的,PC逐渐不再流行了,是守旧的思想在促使很多人一定要买或者组装高大上的PC,就像10年前很多人钟情于目前几乎已经绝迹的大型机一样。在这个后终端时代,买一块可以载有Linux的小板子也许是个不错的主意,它耗电少,无噪音,小型,随便塞在哪里都可以,长期开机也无所谓,它是做家庭上网代理的最好选择了。同事买了一块这样的小板子,据说很不错。现在想在公司的时候也能通过家里的那个小设备上网,需要做些什么呢?很显然,造一个IPIP或者GRE隧道是不错的选择,可是如果想加密的话,就必然要用某种VPN技术,那么首选的是OpenVPN,因为相比IPSec而言,我们有现成的配置。然而OpenVPN参数太多,也麻烦,于是想到了用simpletun,一个几乎仅仅是用来学习的,超级小的,根本称不上项目的小东西,虽然它没有加密功能,但是为它加入一个base64编码支持还是容易的...于是就选择用simpletun建立隧道。
      simpletun本身一点问题都没有,很简单就把隧道建立了,家里的作为服务端,公司的作为客户端,因为公司防火墙只让主动出不让主动进。那么还有什么问题吗?问题来了。用tun模式还是tap模式呢?我比较倾向于tap模式,因为这样就可以把家里的局域网bridge到公司了,公司的机器和家里的小设备以及家里的路由器在一个网段,多好!但是这需要做两个额外的工作:
1.把家里小设备的物理网卡eth0和simpletun启动的虚拟网卡tap0用brctl命令做成一个网桥,然后让这个网桥接管原来eth0上的IP地址,
2.如果公司的机器连接家里的小设备成功,那么其simpletun的虚拟网卡地址也要配置成和家里局域网同一个网段的。
以上第2个问题很好解决,难的是第1个。要知道,对家里小设备的操作是远程SSH上去的,数据包通过家里路由器的公网IP连接,之后被路由器DNAT到小设备的eth0的IP地址,这就意味着在网桥设置期间,必须保证这个IP地址的连通性,但是目前的Linux Bridge机制是不能支持的。
      通观上述过程,其实和虚拟网卡没有关系,于是可以把整个问题转化为:设eth0上有一个IP,为ip,如何找一个办法,使这个ip在下列过程中始终保持连通性,该过程为,将eth0加入一个新创建的网桥。整个问题中,关键的操作有两点,如下所示:
1.网桥起来的那一刻;
2.把eth0加入到网桥的那一刻;
有一点是毋庸置疑的,那就是只要你把eth0加入到网桥,如果eth0收到数据包,内核会认为该数据包是网桥接收的而不再是eth0接收的,包括ARP Reply在内,于是系统的arp表中本来拥有的路由器的arp项:
192.168.1.1 00:11:22:33:44:55 eth0
就会变成:
192.168.1.1 00:11:22:33:44:55 br0 或者 192.168.1.1 (incomplete)  br0(如果br0还没有up的情况下)
要想数据通信能完成,路由结果项的邻居dev字段必须和arp表项的邻居dev字段一致,这也是二层,三层之间的一个一致性渠道。换句话说,要想保持IP的连通性,必须要在将eth0加入网桥的那一刻同时改变路由,而这是不可能的,因为brctl addif操作只完成一件事,即将eth0加入br0!
      如果换一个思路,即先将路由生效,然后再加eth0到br0呢?同样的问题,路由项的dev改变为br0了,然而此时arp项的dev还是eth0!总之就是路由项的dev和arp项的dev在网桥的操作序列中总是会有一个操作导致其不一致,而这一刻的不一致将导致断网,后续的操作将无法完成,就算是一旦不一致了,只能靠另一个配置使其一致,它自己并不会收敛成一致状态。
      其本质原因有两点:1.brctl以及route操作都是原子的,二者互不牵连;2.网桥没有一个中间过渡状态。解决这个问题就是要么将brctl和route关联起来,要么引入一个中间状态。想想就知道,关联brctl和route肯定是不好的,毕竟我们这个需求不是一个普遍的需求,也有很多办法可以解决它,比如设置一个开机if-up脚本,或者写一个批处理后台执行。之所以这么较真儿非要来个online作业是因为我一直都以为自己能解决所有的网络问题...于是就决定用第二种方式,引入一个中间状态,将多个互不相关的触发机制合并为单一的触发,比如虽然调用了addif将eth0加入了br0,调用ifconfig为br0设置了IP地址,但是在那个单一的触发动作没有执行前,一切都不生效。显而易见的做法是,如果br0没有up,则即使eth0加入了br0还依然使用eth0进行数据通信。找到要修改的代码很容易,因为Linux网桥的代码本身就很容易,修改net/bridge/br_input.c的br_handle_frame函数:

struct sk_buff *br_handle_frame(struct net_bridge_port *p, struct sk_buff *skb)
{
        const unsigned char *dest = eth_hdr(skb)->h_dest;
        int (*rhook)(struct sk_buff *skb);

////////加入以下代码
    int flags = p->br->dev->flags;
        if (!(flags & IFF_UP)) {
                return skb;
        }
////////
.....
}
然后加载修改后的模块后执行以下序列:
1.新增网桥
brctl addbr br0
2.关闭STP(可选)
brctl stp br0 off
3.设置一个掩码稍长的IP,暂时设置为down状态
ifconfig br0 192.168.1.100/25 down
4.加入eth0到br0
brctl addif br0 eth0
5.开启br0
ifconfig br0 up
6.清除eth0的IP
ifconfig eth0 0.0.0.0
值得一提的是上面第3个步骤,Linux中如果不同语义的相同路由是添加到一个list的尾部的,因此如果两块网卡配置相同的IP地址,则谁先up谁的链路路由在前面优先被匹配,设置掩码稍长的IP地址是为了让br0的网段比eth0的网段更加精确,但要注意,默认网关一定不能被25位掩码和br0的IP劈开到不同的网段,比如默认网关是192.168.1.128后面的地址就不能,因为它已经和192.168.1.100不在一个网段了,如果实在想使用稍长掩码的IP地址,你就要增加一个force onlink的路由。其实可以用metric来做到上面的说的而不用稍长掩码的IP地址,也可以修改Linux内核的路由部分,将fn_hash_insert中的某处list_add_tail改为list_add_head(此处不说了,感兴趣的自行修改)...
      事情就这样解决了,但是却无法落实!什么叫落实呢?就是说形成一个通用的方案,我想如果我把修改后的bridge机制提交给kernel maillist,肯定会有很多人骂的,更有可能的是根本没人搭理我...从代码风格上,这种硬编码方式不可取,从实际效果来看,你不能保证使用bridge的人一定就同意你的逻辑,因此做成可选的选项就比较好,怎么个可选法呢?模块参数当然是其一,但是还有更好的方法。
      行文至此,有一个疑问,为何不用ebtables的broute表呢?比如你设置一条:
ebtables -t broute -A BROUTING -j DROP
这条命令的效果和上面修改代码的效果一样,可是问题又来了,当你执行brctl addif br0 eth0的时候,就会断网,因为此时你需要删掉那个规则,然而已经没有机会删除了,因为你再也连不上去了。实际上,这也是单一触发的问题,每次操作仅仅触发一个动作,和上面讨论的问题实质是一样的。之所以在本文快要结束的时候引入一个新的问题,是为了展示一个更好的方案。我们来看看上面的ebtable命令缺了什么,缺乏的正是一个match,即对br0状态的判断,它才会一股脑地将全部的数据都DROP到上层,如果它成为以下这样子的话:
ebtables -t broute -A BROUTING -i br0 -state --dev-state up -j DROP
那岂不就完美了,丝毫不用改现有的Linux Bridge逻辑,需要做的仅仅是增加一个ebtables的match模块,这种模块化的东西本来就是让扩展的,至于怎么扩展,就看ebtables有没有iptables灵活了,好像是没有,因此我也就在此作罢了。
      这明显不是什么吐嘈,纯粹是自己比较无聊而写的一篇技术指南。随便吧!让人吐嘈的不是这篇文章所表达的内容,而是我对信息记录的极大不满,因此,真正的吐嘈这才真正开始。
      我越发觉得使用智能手机是多么危险的一件事,昨晚被老婆查手机,查完后怒了,不是针对老婆,而是针对没天理的APP,我曾经去过哪里,在那个地方待了多长时间,通过坑爹的iPhone隐私都可以查到,通过另一个APP,就可以查到曾经所有的通话记录,短信,都上过哪些网站,而最令人气愤的是,你删除的东西可能不是真的被删除,依然留在机器的某个角落,比如/var/cache/...我说一个具体的困境吧,如果你用iPhone下载了新浪微博,用一个帐号登录了,然后退出,这个账号就永远存在于你的手机了,下次,你只要输入账号的第一位,就会自动补全,好吧,你找不到任何地方可以删除这个登录信息,那么怎么办?直接将程序删除,然后重新下载,这下清静了,不能自动补全了,看似清除了登录信息,好吧,是的,真的清除了,这个时候不要登录,等待,等待,然后突然收到一条那个登录过账号的特别关注好友的一条新微博,天啊,我还没登录呐,怎么就知道那条微博要推给我啊...以上是一个真实的事实,我故意这么测试过。
      微信也有问题,我把一个不宜公开的***拉到了朋友圈黑名单,然后再把他删除,可是他的更新我还是可以看到。诸如此类的还有很多,感兴趣的自己去玩吧。别跟我说技术不成熟导致的问题,在我看来技术永远都不可能成熟!技术至上的观点多么天真,对于只懂技术或者对生活中的其它事情不关心不理解的人,一不小心就被他人引入一个万劫不复的深渊,深不知深究此事是多么的无聊,其实很简单,你自己做一个坏东西让别人折腾去呗,都别人玩儿又不犯法。你要知道,所有的好点子都不是技术人员搞出来的,都TMD是小丑引申的。留声机刚发明的初衷并不是记录音乐,甚至觉得记录音乐是亵渎了这个该死却又伟大的发明,可是事实呢?
      当你走在街上,大量的摄像头指着你,就像有狙击手在暗处瞄准你却不开枪给你带来的感觉一样,因为一旦他开枪,一瞬间你就不会有任何感觉了。在货梯旁边的楼道吸烟的时候,可能保安正盯着你看你在通过手机看什么内容,如果你看的是禁网,他们也会受惠。银行的流水帐单会告诉别人你在什么时间什么地点在哪家店消费了多少钱,如果你狡辩说你在穆斯林商场的经纬度地点买了一斤猪肉,那你肯定在说谎,因为你根本不知道注册的信息有哪些;门户网站,iPhone,公司的NAS,它们都会记录你的信息,你也不知道这些信息最终会被如何处理,最简单的办法就是TMD少进行信息交流,虽然我不会上公司不让上的网站,但我也同时拒绝了使用google查阅正规的技术内容,我拒绝使用互联网,仅此而已,虽然我不会去东莞,也不会跟陌生女人通话,但我还是删除了所有的通信录和APP,我拒绝使用智能手机,仅此而已。
      闭上眼睛,在厕所撒尿和在人民广场马路中心撒尿,对于没有心智的人而言是一样,可是处在时刻被监控状态的网民,实质上和闭上眼睛在广场中心撒尿没有什么两样。对于少数技术人员而言,总是可以在撒尿的时候罩上一块简单的屏障,甚至直接睁着眼撒尿即可,同时还向四周竖起了中指,但对于大多数技术人员而言,为了撒尿,却只知道自己憋着尿盖一座厕所,如果你想如厕,旁边商场里就有啊。无知导致滞后...