首页 > 代码库 > Linux 4.10中两个新特性与我的一段故事

Linux 4.10中两个新特性与我的一段故事

今早5点半起来没有开始写文章,而是去西湾红树林连跑带走折腾了将近20公里,回来后就8点多了...洗了个澡之后坐稳当,开始写一段关于我的故事。
        在2014年到2015年期间,我在负责研发一款无线安全网关,其实就是一个VPN,接入设备包括手机,xPad,盒子...这些设备的OS除了iOS之外,基本上都是基于Linux的Android。这个网关一般用于各种需要高性能加密通信的场合,在数据传输之前需要比较强的认证,服务端支持4G的加密带宽,支持复杂的接入控制和访问控制,支持复杂的Qos,另外,还可以将两台甚至多台VPN网关设备对接形成一个复杂的网状混合拓扑,支持网中网,功能非常强大...广告做到此为止。
        现在开始说说接入设备,也就是客户端的一些事。
        在研发以及部署期间,其实最头疼的不是网关端,而是接入设备端,也就是那些Android客户端。
        典型的需求是,为一组App构建一个沙盒,沙盒里的App走VPN通道到网关,而沙盒外的App不受任何影响。比方说,交警使用的移动警务App的数据显然要进行保护,而交警在轮班期间无聊时看看新浪微博则不应该受到影响,且移动警务App和新浪微博不能互访。
...
我曾经花了比较多的精力去跟客户解释我们的Android客户端多么的“智能”,可以保证特定的且仅仅是特定的App的数据才会进入VPN隧道,这其实是MDM厂商的活儿,对于我的产品,这只是个附加的功能。于是,为了实现这个功能,就不得不不断调研Android的某个版本在非root情况下是否可以操作路由表,操作iptables(这个可能性不大),可不可以NAT,如果可以的话,是不是通过一个本地监听的代理程序可以完成导流的工作...对于以TUN网卡实现的VPN而言,其实这一切工作归根结底只是一个需求:让特定的App数据发出时走特定的路由!
        可惜,在之前的Android内核版本中,这么一个简单的需求实现起来非常不易。现在Linux 4.10内核带来了一个新的特性,即为不同的uid配置不同的策略路由,这足以解决我的问题,只是晚了大概两三年:
Add support for per-UID routing. It allows the administrator to configure rules such as:
# ip rule add uidrange 100-200 lookup 123.
This functionality has been in use by all Android devices since 5.0. It is primarily used to impose per-app routing policies (on Android, every app has its own UID) without having to resort to rerouting packets in iptables, which breaks getsockname() and MTU/MSS calculation, and generally disrupts end-to-end  connectivity

另一个比较激动人心的特性来自于路由和iptables的联动。
        早在2014年年底,我在OpenVPN的程序里面内置了一张路由表,这是为了防止有人手工添加一条路由,这样就可以访问不该访问的资源了。也许你会觉得在服务端做足做够访问控制不是很好吗?是的,这个我也知道,我自诩精通OpenVPN,从源码到配置再到优化重构,怎么可能不知道这个。令我激动的是Linux内核终于提供了一个不依赖OpenVPN的通用机制来实现这个需求:
Introduce an nftables rt expression for routing related data with support for nexthop (i.e. the directly connected IP address that an outgoing packet is sent to), which can be used either for matching or accounting, e.g.
# nft add rule filter postrouting ip daddr 192.168.1.0/24 rt nexthop != 192.168.0.1 drop
this will drop any traffic to 192.168.1.0/24 that is not routed via 192.168.0.1
看吧,这是使用nftables而不是iptables配置的。这并不是我对nftables的又一个鼓噪点,而是想表达iptables已经逐步迁移到nftables了。要是在两年前,我肯定会写一个iptables的模块,但是现在不会了。
...
现在说说我的另一段故事,可从中窥见很多网络技术。
        曾经有四个难题让我没日没夜几个月,不过幸运的是,这些问题最终都被我用Netfilter解决了:
1.路由查找与转发开销比较大
起初我是借助优化后的nf_conntrack来解决问题的。总的来讲就是将IP层的处理进行短路,借鉴于Cisco的CEF技术分离出一个转发表,然后将表项保存在conntrack项中。后来我干脆抛弃了conntrack,而是直接使用了DxRPro这个结构来完成查找,具体参见《以DxR算法思想为基准设计出的路由项定位结构图解》。
当初之所以使用conntrack是因为它可以高效定位一个流并保存流信息,而转发项可以保存在流中...如果有一种比定位流更加高效的方式,那就不需要conntrack了,事实证明我找到了这种更高效的方式。
2.过滤条目太多,导致了收包性能急剧下降
饱受诟病的iptables filter被我彻底放弃了,取而代之的是nf-HiPac以及ipset,后来我又发现了更好的方式,当我在路由查找中把conntrack放弃了之后,我将其引入到了ACL匹配。具体来讲就是,我把流的首包ACL结果保存在conntrack表项中,后面直接使用,无需再匹配。当然,我的conntrack是被我优化过的,不是原生的。
3.Linux中无法配置双向静态NAT。
关于Linux的NAT,它本来就是保存在conntrack中的,仅对首包匹配NAT规则,然后对于后续的包直接从conntrack取出后使用。所以说就无法是静态的,也无法是双向的。我的做法是简单的实现了一个新的NAT机制:https://github.com/marywangran/static-stateless-2-way-NAT-on-Linux-with-iptables
        所有以上这三个问题,除了最后一个,总的思路,就是一次查找,保存后多次使用。
        面对这三个问题,解题思路以及解决方案几乎占据了我这个博客的大量的篇幅,我也因此得到了公司的认可以及新的工作机会。然而如果就此停步不前,那就可悲了。
        网络技术一直都在迅速发展,每天都有新的东西冒出来,必须不断学习才能永远被公司认可,永远不缺新的机会。
4.Linux Bridge问题
我清晰地记得我在2010年3月19日去我上一家公司面试时被问到的一个问题:你对Linux Bridge有了解吗?我说有了解,于是我就试着说说看...其实那个时候我也只是看过Linux Bridge的源码而已,好在面试官(后来成了我职业生涯中都要感谢并永远不会忘记的恩师...)所在的业务组并不以这个Bridge为主导,所以我也就蒙混过关了,充其量当时我也就知道Brdige的大致原理,更进一步的东西也说不清。
        我本以为从此以后再也与Linux网络无缘了,可是非也...到了2011年底,我要做的事情奠定了我今后的技术路线。
        说实话,我做的VPN产品可能囊括了所有的Linux网络技术,从下到上说吧,PCIE,MSI,软中断负载均衡,Bonding,Spanning Tree,Bridge调用IP Netfilter,IP层Netfilter,IP路由,策略路由,TCP分发,TUN字符设备,TLS加密解密(对称加密,非对称加密,摘要...),HTTP,HTTPS(我尽力去学习,却最终也没有学会http相关的应用层...),VRRP,HSRP,OSPF...期间我学会了ECC椭圆曲线算法,也知道了什么叫做双证书认证,但是我觉得对我最重要的是我在2013年就修改了Linux Bridge的逻辑,使得其符合了SDN的思想!
        What?Why?
        2013年的时候,SDN已经被提出很多年了,但是我并不知道这个概念,我确实是在2013年中下才知道的这个概念,好像当时还写了几篇软文(一般不懂装懂的人都会写软文),但是明白了SDN深意之时却在多年以后。
...
我关注SDN是因为我觉得它跟我当时正在做的东西的思路不谋而合。我当时在网络层需要一种集中的控制机制,不管是路由策略还是QoS,都需要在一个中心端来集中控制,然后把策略下发到各个子节点,子节点没有什么智能逻辑,它只是盲目执行中心端下发的策略而已,我这种做法看似是与分布式网络控制机制背道而驰的,但我厌倦了Spanning Tree,RIP这种不可控的东西,同时我也不喜欢TCP,不喜欢区块链,我需要找到一个理论依托来支持我的独裁式集中控制机制,后来在一天下班的路上,我用手机看技术网站,发现了SDN这个概念...
        这个跟Bridge有什么关系呢?
        我当时希望数据包绕过IP路由,直接执行集中式的VPN中心端下发的策略即可,这些策略不仅仅包括路由,还包括别的很多其它的,比如是二层透传还是三层路由。所以说我需要自定义一个匹配链,每一个数据包都要去匹配这个匹配链,一旦命中便执行该匹配项指示的动作,类似iptables那样。不过是直接借用了conntrack连接跟踪。这样匹配链就可以直接用conntrack本身了,然后在conntrack项中增加一个action字段,数据包进入内核协议栈之后会被无条件关联一个conntrack表项,然后直接执行该conntrack表项指示的动作即可。
        但是大家都知道,nf_conntrack是作用于PREROUTING点的,这已经位于IP层了。我希望的是在Bridge层就做匹配,然后根据匹配好的conntrack表项的action来决定是直接Bridge层转发还是broute到IP层。幸好,Linux Bridge有一个call ip netfilter机制(详见/proc/sys/net/bridge/bridge-nf-call-iptables),正合我意!可是这个call点是在决定了是否Redirect之后才执行的,于是我不得不颠倒二者的位置,参见《关于bridge-nf-call-iptables的设计问题》。
        在我的这个设计中,系统的IP路由机制几乎是不起作用的。子节点VPN守护进程会从中心节点接收转发策略,并注入到conntrack表项,然后就可以来包转发了。我编程编的不好,但我对Linux内核协议栈流程非常了解,所以我可以做到利用一切现成的东西,小修小改即可满足我的需求,我借用了conntrack,我借用了bridge-nf-call-iptables...
那个时候我还不知道OVS(即Openvswitch),要是知道有这么个玩意儿,我连conntrack,Bridge都不用了,仔细看看,OVS实现我上面的需求,是不是很直接呢?本质上来讲,OVS就是强化版的Linux Bridge,以前需要你前挪后移搬运的功能现在都集成在OVS内部了。OVS内部就有一个流表,而且还是多级的,可以接收SDN控制器注入的流表项,一切都是已经标准化的,现成的东西也多。我要是早几年知道有这么个OVS,那得减少多大的工作量啊。
        我还没提VxLAN呢,也没提LISP,其实本质上它们都是建立大二层的技术,我做事随意,崇尚草根之风,用OpenVPN建立了大二层,盖在了上海市的一个城域网中,这不就是Overlay么...
        至于NFV,也不过是一个名词。我很怀念我的那个花了无数个日日夜夜做成的东西,它里面包含了太多的东西,如果掌握了它,你也就掌握了网络的核心技术之脉络,在开发期间,接触了各种Linux内核网络的东西,在部署期间,有幸与CCNP/IE们各种交锋,掌握了核心网的很多东西,甚至在送货的路上,深夜回家的路上,都会突然冒出很多想法...故事还没有讲完,但我不想再讲下去了。对于给我机会做这事情的经理而言,除了感谢,还是感谢!
        下面准备说说BGP和DCI的事情...如果明天有时间的话。

Linux 4.10中两个新特性与我的一段故事