上帝管上帝的,凯撒管凯撒的!耶稣这样说过。
如果这句话传到包容的罗马皇帝或者罗马元老院耳朵里,估计就没有基督教了吧,只是那可恶的总督本丢.彼拉多和犹太权贵勾结,滥用了职权,才使耶稣成了基督,一起简单的聚众布道事件变成了殉难,世界由此不同了...我并不赞成爱德华.吉本的观点,将罗马帝国的陨落归罪于基督教,在我看来,罗马帝国一直存在至今,虽然采取了更加抽象的方式。
在TCP/IP网络的世界,分层设计的宗旨就是要实现“上帝管上帝的,凯撒管凯撒的”,然而也有一个本丢.彼拉多,将事情变得复杂,使TCP/IP这个罗马式的帝国变得不包容,虽然没有基督式的殉道,也差不多使互联网发展变缓趋于停滞,这个本丢.彼拉多便是TCP!
在TCP/IP刚被设计的年代,即互联网的公元元年,主机是固定的,用于编址的IP也是固定的,世界是和平的,可是随着应用程序以及芯片技术的活力涌现,设备越来越小,App越来越丰富,当你觉得浑身憋得慌的时候,就动起来,移动IP时代来了。可是传统的TCP并不适合移动IP,传统的TCP基于TCP/IP协议头字段的五元组,而标识设备的IP仅仅标识了设备位置,并没有标识设备本身(--实际上不管到了什么年代,IP地址都不应该标识设备本身,它就是标识位置的!问题是,TCP不应该用一个标识位置的元素来标识设备。)一旦设备换了位置,其IP也会改变,进而既有的TCP连接将全部中断,这对于应用层来讲是不可忍受的,于是各种弥补技术就出来了,本文只是大致介绍一下,因为本人很不看好这些技术,本文将留下大量的篇幅介绍本人觉得合理的技术。
解决方案1:向下对IP层动手术
这是传统意义上的移动IP技术,主要指RFC 3344,主要使用了代理和隧道技术,包含很多的组件和过程,极其复杂,其宗旨就是即使位置改变了,IP改变了,设备之间依然可以使用其发起连接的时候的TCP五元组进行通信,这样就保证了TCP连接感知不到设备的移动,保证其不会中断。举例说明一个最简单的情况,那就是使用隧道技术,将老的IP数据报用隧道协议封装在新的IP数据报里面。在最初学习该技术的时候,总觉得它怪怪的,虽然它也模拟了很多人类的社会行为,比如搬家,快递转交,手机换号通知等,但是这些拟人化的行为无法弥补我的怀疑,传统移动IP技术本身的设计是绝对棒的,错不在它,传统移动IP技术之所以复杂难以驾驭,是因为传统TCP技术的设计缺陷,巨大的设计缺陷当然需要巨大的代价来弥补,可惜TCP/IP的进化方式并不是完美的进化方式,完美的进化方式应该像有性生殖生物的进化方式一样,一切个体从受精卵开始重新被“设计”,重新发育,生殖细胞重组以及发育过程中的变异缓慢积累下来接受自然选择。TCP/IP技术无法做到让新技术从受精卵开始发育,它只能做到技术重组的变异累加,所以任何人都无法砍掉一些东西,即使它已经很不合理了,能做的只是用一块合理的布把它包在里面。
说实话,要不是为了兼容传统TCP,那些大佬们绝对不至于设计出如此的东西来。
解决方案2:向上对应用层动手术
上面一节中我谈及了许多个人观点,那些观点自然而然被带到了这一节,因此就不再重复。所谓向上对应用层开刀指的是在应用软件和传统TCP之间加一块“合理的布”,用于隐藏下面IP的变化,也就是说,应用程序将不会直接面对IP,建立连接的时候,也不用再提供远端IP地址,只是提供一个类似域名之类的标识即可,如果设备更换了IP,应用发出的数据将暂时被排入队列,然后中间层迅速重建针对新IP地址的连接,发出排队的数据,所有的队列管理,连接重建,连接修复工作全部在这块“合理的布”下面做。典型的技术是IETF的Multipath TCP,不那么典型的技术也有,包括一些技术爱好者,大牛们自己实现的,其中有云风本人的所谓“更稳定的 TCP”http://blog.codingnow.com/2014/02/connection_reuse.html。如果说解决方案1中所说的传统移动IP技术需要运营商的支持的话,那么本节所述的技术则需要App开发者给与支持,所有的App将必须使用新的API,而不是传统的socket API,如果说兼容性是TCP/IP要考虑的第一要素的话,这种对应用层动手术的方式无疑自己将了自己一军,已经发布出去的App怎么办?如何平滑过渡升级?过去的2013年一整年,我考虑的最多的就是平滑过渡升级,保持兼容性,也因此被公司评了优秀,沉甸甸的银子拿在手,我绝对有权说我对兼容性以及平滑升级的理解即使不很到位起码也值得参考。
改掉TCP,杀掉本丢.彼拉多
TCP深深嵌入了IP,这种说法也许并不准确,当初是没有TCP/IP分层的,二者是一体的,后来有人按照分层设计的原则将二者分离成了两个层次,但是分离的并不彻底,二者并没有真的做到各司其职,正和本丢.彼拉多和自治的犹太人之间的关系一样,作为罗马的行省总督,他不明白自己的权力行使边界在哪里,虽然没有给他本人带来什么灾祸,却让爱德华.吉本认为给整个古代世界带来了灾祸。我之所以用本丢.彼拉多举例是因为犹太行省和罗马帝国之间的关系,正和TCP和IP之间的关系一样,是自治和承载的关系,正如本丢.彼拉多和犹太贵族之间的关系一样,TCP和IP的关系并不纯粹。1.TCP作为端到端协议,将“端”这个概念映射到了IP地址上,用IP地址识别对端;
2.TCP校验和包含了IP层伪首部,当时使用伪首部的动机以及理由目前已经不存在;
既然一切都是TCP这个当今的本丢.彼拉多惹的祸,那就干掉他!
若不是还想保留TCP的语义,我觉得重新设计一个传输层协议也不是不妥,相反,会更好,因为TCP真的太烂了。为了保留TCP的语义,因此,我保留TCP协议的格式,仅仅改变了TCP校验和的计算方法以及TCP连接的查找算法:
1.不再使用伪头部,TCP校验和仅仅针对TCP数据段进行计算;
2.TCP连接的查找不再基于五元组,这个比较复杂,需要详细说明。
传统的TCP实现中,TCP数据段到达传输层之后,会用{源IP地址,目标IP地址,源端口,目标端口}这四元组来将一个TCP数据段和一个TCP连接关联起来,最终落实到一个socket上,可以看到,其中的源IP地址和目标IP地址的存在,使TCP和IP有了很强的关联,导致了移动IP实现技术的复杂,要想简化移动IP,就需要解除这种上下层之间的相互关联,实际上,复杂的并不是本质意义上的移动IP,复杂的是移动IP技术对TCP协议的包容!而这是没有意义的。取而代之的,我们为了实现TCP的端到端语义,就需要找一个真正能标识TCP端点所在设备本身的字段,该字段可以理解为“设备地址”,在编址意义上,该字段是全局唯一的,并且只要设备不变,该字段的值就不变。因此使用设备厂商ID+序列号或者设备网卡的MAC地址都是合理的选择,需要注意的是,这个字段值的选择必须标准化,否则的话,设备厂商ID+序列号有可能和某MAC地址重复。总之,在全局唯一性这个需要上,和采用源IP地址,目标IP地址的方式是一样的,不同的是,新的设计中,该值是和设备绑定的,是不变的,不会像IP地址那样随着设备的位置而变化。
新的瘦身版TCP的“设备地址”字段非常重要,类似LISP协议,名称和位置标识符分离。插说几句,LISP(并非LISP语言)的出炉更多的也是要满足云环境中无所不在的设备移动性以及虚拟化的需求。和我的新版TCP不同,LISP是在网络层实现的,只是寻址意义上的,如果LISP全面铺开并且标准化,我的新版TCP也就不需要了,虽然也是在网络层动手术,但是这个手术比较传统移动IP技术而言更加成功和彻底。如果采用LISP方案,那么TCP可以保持不动,依然让伪头参与校验和计算,依然采用基于源IP地址,目标IP地址的TCP连接查找算法,只不过这里使用的IP地址是Location-ID Separation Protocol中的ID这个地址而不是Location地址,你完全可以把Location地址视为现如今我们普遍使用的IP地址,而ID地址则目前在标准IP协议中还不存在,另外,换一种理解方式,你可以把Location地址视为隧道外层的地址,把ID地址视为隧道内层地址。在LISP正式形成规模前,如果你想达到这样的效果,只能在TCP层面上做文章,因为没有运营商的支持,你能控制的只有你自己的端系统。
扯了那么多的LISP,目的是想澄清一种思想,即位置地址和ID地址分离是大势所趋,并非我凭空想象的。现在回到我的新版TCP实现上来。由于我仅仅修改了校验和算法和TCP连接查找算法,因此所有TCP的语义被完整保留,诸如流控,拥塞控制,滑动窗口之类的行为全部不变。
为了使得新版的TCP兼容传统的TCP,我把“设备地址”机制放在了TCP选项中,定义了一种新的选项类型,值就是“设备地址”,如下图所示:
处理兼容性的时候,只有在TCP段拥有这个设备地址选项的时候,才会以设备地址为键来查询TCP连接,否则执行传统TCP行为,如下图所示:
注意,这个设备是双方都拥有的,因此在新版TCP行为的实现中,必须对三次握手的细节作必要的调整,开始建立连接的时候,即SYN发送时,发起方携带自己的设备地址,目标设备的设备地址置为全0,接收方收到SYN后,初始化序列号以及自己的设备地址,在SYN/ACK包中携带自己的设备地址以及发起方的设备地址,同时将该TCP连接以源端口,目标端口,源设备地址,目标设备地址为算子计算一个哈希值,置入哈希表中(注意,此处暂不考虑syn-cookie机制),此SYN/ACK包和发起方的SYN包一样,依然使用IP地址寻址TCP连接,即传统的TCP方式,发起方收到接收方的SYN/ACK后,使用源端口,目标端口,源设备地址,目标设备地址为元素计算一个哈希值节点,替换在SYN包发起前以源端口,目标端口,源IP地址,目标IP地址为因子计算的哈希值表示的节点,接下来发送ACK以及数据,三次握手完成,接下来的TCP数据段和TCP连接的关联完全以设备地址为计算因子,这样即便是IP变化了,TCP连接依然有效,如下图所示:
TCP就这样被改造了,接下来你也许要问,怎么保证TCP数据段能到达目标主机呢?这是IP的事,即便在传统TCP中,这也完全是IP的事,你见过中间寻址路由器用TCP协议字段来做路由寻址的吗?少扯IP mark以及Policy Routing,我说的是标准的IP路由。事实上,TCP协议虽然是端到端协议,但是寻址到端根本没有TCP什么事,全部是IP的问题,是IP协议确保了端主机到端主机的可达性,TCP所谓的端到端中的“端”指的是进程而不是主机!
TCP卸载了大量IP相关的计算后,IP变得纯粹了,传统的移动IP技术也将被彻底改写,几乎不再具有存在的理由。剩下的问题就只是IP地址在变化的情况下如何做到主机到主机的可达性保证了,说白了就是一个超大规模的动态路由问题。纯粹的移动IP
如今最大的分布式系统就是人类社会,但是即使人类社会也被分成了各种类型的国家,每个国家拥有自己的政府以及各类社会组织,每个政府以及组织拥有自己的组织结构,并且存在一个核心,该核心执行一对多的政策下发或者公告之类,这表明,如果玩不转完全的P2P,分布式的话,最好有一个中心。因为在节点数量巨大的分布式系统中,即使一个微小的设计bug,都将会带来指数级爆发的故障,除非该系统是自组织的,但是据我所知,目前的IP网络并不是完全自组织的,特别在某些地区。IP协议,特别是移动IP,需要支持分布式自学习辅助以中心控制。目前,ICMP并没有被好好挖掘,更多的网关要么不用该协议,要么干脆屏蔽拦截掉它,在移动IP年代,ICMP将会拥有用武之地。IP的漂移,设备IP的改变,这可以被看作是一种事件,而对这些事件的处理则是控制平面的事情,典型的处理有三种:
1.更新路由表;
2.告知端主机;
3.安全相关(审计,计费...);
第1点和第3点不是本文考虑的,关键是第2点,IP变化了,如何告知参与通信的对端呢?注意,告知对端的目的并不是怕TCP断掉,在使用了我的新版本TCP协议之后,已经没有这个问题了,最关键的问题是对端在封装IP头的时候需要使用新的IP地址,这个在SDN年代很简单,IP更新事件逐层报告给一个Controller,该Controller将该消息逐层下发给另一端,期间所有的使用老IP的数据包,Contraller指示做NAT。然而在非SDN环境中,你就必须采用既有的协议的方式来完成IP地址更新通告,首选的就是扩展ICMP协议。
新的移动IP协议新增了以下的ICMP消息:
ICMP Checkchange:{ int type; //0-直接携带了新老IP地址;1-需要查询就近的controller union { struct { uint32 old_addr; uint32 new_addr; char options[0]; }; uint32 controller_addr; }; };为了支持该消息,需要修改标准的IP协议处理流程:若地址不可达,则回复新老IP映射或者中心控制器的地址,中心控制器做最后的抉择,它也是新引入的,内部保存有一系列组织成哈希表的映射,每条映射保存一个新IP地址和旧IP地址的映射。中心控制器存在多个,组织成树形层级结构,最坏的情况,则是遍历所有。事实上这种最坏的情况几乎是会碰到的,因为移动IP设备基本上都是平滑移动的,移动的路径在物理上是连续的,所谓的就近中心控制器对于连续的几个IP地址保有段的基站都是就近的,符合局部性原则,即便跨越了一个中心控制器,也可能通过查询同层的前后兄弟节点而快速命中,关键是中心控制器的部署拓扑以及查找算法问题(实现技术可以多样化,memcache之类的也可以用)。
事实上,纯粹版本的移动IP要比你想象的简单,新的ICMP Checkchange控制消息并不是每种情况都会被用到,IP地址的改变分为以下几种情况,假设HOST A和HOST B通信:
1.A的地址发生改变,发送数据到B,B收到数据后,用A的新的IP地址作为目标IP作回复;
2.A的地址发生改变,等待接收B的数据,此时A将IP地址改变事件发给全局可达的中心控制器(存在多个,类似DNS),B此时仍然以A的旧地址封装IP报文,当该地址不可达的时候,不再像传统IP那样回复ICMP Unreachable消息,而是回复新的ICMP Checkchange消息,包含中心控制器的地址,B收到后以A的旧IP地址去查询中心控制器,得到A的新地址,如果中心控制器没有查到A的新地址,则回复ICMP Unreachable消息;
3.A和B同时改变地址,则同时执行上述的情况2的步骤。
由此可见,在移动IP情形下,路由不可达不再是一个绝对化的错误了,有可能并不是不可达,而是设备更换了IP地址,具体是哪种情况,还得由中心控制器说了算。在上述说明中,和TCP几乎是没有任何关系的,即便是UDP,通过中心控制器也能让端主机平滑识别和确认对端主机的新IP地址。IP层变成了纯粹的转发层,不再和传输层有任何关联,同时最重要的是,不再需要传统移动IP技术中的那么多复杂的组件和概念了。
动手实践
抽几个下雨的夜晚,把上面的变成现实!从设想到实现,我都选择了下雨天,每逢下雨天或者雨夜,我就比较兴奋,效率超级高!直接修改Linux内核协议栈?开始的时候是这么想的,起初以为只改一点点就行,但是改了一点点后发现牵扯出来的东西还有另外一点点,没完没了,每次的panic让美好的雨夜变成了煎熬!于是改成了uIP,这玩意儿比较好玩,用户态轻量级,Linux下繁重的工作转变成了uIP下蹂躏的快感......一点感想
干掉了本丢.彼拉多以后,想了很多。网络协议已经慢慢退化成了仅仅的数据格式,它已经渐渐失去了控制层面上的一切。以往所有的东西都是协议本身控制的,比如TTL到期后将丢弃,路由不存在将丢弃等等,即使你想让一个TTL到期的包继续前行也不可能,然而在SDN年代,控制逻辑全部由软件自由定义,协议已经不再有约束力,协议是什么?协议是一个数据格式,便于操作系统内核知道该如何容纳数据,数据有多大,如何分配内存,什么数据放在什么位置等等,协议在交换机中仅存的作用就是便于从某个偏移取出某个字段,仅此而已!
也许,你会认为我的想法以及本文描述的移动版TCP/IP都太简单了,是的,核心的东西就是这么简单,就应该这么简单,这样它才能经得起复杂化,我不会像写博士论文那样去思考问题,不管什么问题都是如此,技术的,历史的,哲学的,不会面面俱到,也许还稍许偏激,但是却是一目了然的,一目了然。
以上,纯粹个人的胡思乱想,不辩论,不较真。请不要用个案以及特殊环境下的个例来反驳,在那种情况下,我的所有想法都是大错特错!