您的位置:首页 > 理论基础 > 计算机网络

Linux内核导论——网络:TCP效率模型和安全问题

2016-01-01 09:08 821 查看

TCP速度与带宽

有人认为TCP可以以带宽的速度发送数据,最起码用带宽扣除TCP包头损耗就是TCP传输可以达到的最大速度。这个理论是正确的,但很多时候TCP的速度却达不到带宽。

TCP由于采用拥塞避免算法,并不总是以实际带宽的大小来传输数据。尤其是在共享带宽的时候,带宽到底有多大,这是个不好说的问题。

TCP受制于系统资源,还需要设置缓存大小,上层应用接收不及时的话缓存满了,TCP想快也快不了。

对于大量短连接,很大的带宽开销用来维护TCP连接,而不是传输数据。

还有很多情况TCP可能都无法建立,例如内存,backlog,端口数等。所以TCP虽然在直观上是用来高效的传送大量数据的,但是必须要认识到TCP很多情况下并不是带宽。

UDP内核代码路径

UDP读取过程

从上向下调用部分:

sys_read fs/read_write.c

sock_read net/socket.c

sock_recvmsg net/socket.c

inet_recvmsg net/ipv4/af_inet.c

udp_recvmsg net/ipv4/udp.c

skb_recv_datagram net/core/datagram.c

从下向上中断部分:

sock_queue_rcv_skb include/net/sock.h

udp_queue_rcv_skb net/ipv4/udp.c

udp_rcv net/ipv4/udp.c

ip_local_deliver_finish net/ipv4/ip_input.c

ip_local_deliver net/ipv4/ip_input.c

ip_recv net/ipv4/ip_input.c

net_rx_action net/dev.c

UDP写入过程

从上向下部分

sys_write fs/read_write.c

sock_writev net/socket.c

sock_sendmsg net/socket.c

inet_sendmsg net/ipv4/af_inet.c

udp_sendmsg net/ipv4/udp.c

ip_build_xmit net/ipv4/ip_output.c

output_maybe_reroute net/ipv4/ip_output.c

ip_output net/ipv4/ip_output.c

ip_finish_output net/ipv4/ip_output.c

dev_queue_xmit net/dev.c

http://www.cnblogs.com/better-zyy/archive/2012/03/16/2400811.html

当前TCP设计的充分利用

从这两段路径你可以看出什么?是的,在linux中接收既有从下向上也有从上向下,而发送只有从上向下。这很容易理解,但也很容易被忽视。人们在使用linux的时候经常遇到网络速度不尽人意,并且是在带宽足够的情况下。内核大部分情况是能够充分的使用带宽的,不能的一般是用户的程序的问题。比如收的不及时,收完了很久才发送,这是业务层次的数据空闲窗口。

接收部分是最考验协作能力的地方,因为它需要3个单位精确的协作。软中断、内核socket代码、用户端代码。其中软终端还是异步的,而内核的socket则完全听命于用户程序是否调用。由于实在没有办法让用户程序和软中断完全同步,所以,这一步阻塞或旋转等待几乎成了用户端程序的唯一解决方案。但是在接收到连接还需要处理,这个处理的过程没有在接收新的连接,导致信道再一次不可用。

现代的解决办法以nginx为代表,使用多个线程同时非阻塞监听,每一时刻一定有多个在监听,还可以有多个在处理。监听不阻塞的好处是,一个线程在监听的同时还可以处理已有的连接。传统的fork多个子进程,或者用多个伺服线程的做法在nginx这种所有worker线程都是server和client的高效思想面前几乎一无是处。

所以,你也可以看出来了,用户端的程序员比较勤奋,是他们最有效的解决了协作问题。内核端不能解决吗?非也,懒。把nginx的这套高并发的思路移植到内核里效果会更好。不过ATM还比IP好呢。。。

但是nginx这种思路有一个问题:就是惊群效应。多个进程同时监听,虽然不多,但是在多核机器上十几个进程同时惊群还是有不少损耗。为了解决这个问题,nginx会在监听的时候加锁,保证同时只有一个进程在accept,这又是一个问题,其他进程如果手头没有工作就会在空转等待。但是理论上,这已经是非常优秀的让所有进程都充分忙碌的解决方案了。

另外在客户端编程中还发现了一种高效的并发模型,就是traffic server的单个进程监听(traffic manager),多个work分发处理(traffic server)。与传统的线程池模型相比,traffic server有利用协程的概念,其将服务一个连接的上下文封装为一个协程,交给worker进程调度处理,在有限的worker进程中可以处理无限的连接。这一点也与内核的软中断机制不谋而合。最重要的是traffic server的这种模型允许扩展成云,因为协程本身就携带了完整的执行信息。

更高效的TCP

综合上述两种,能够最充分使用内核TCP基础设施的方法是单监听,甚至可以多个进程排队监听,异步分发上下文到worker进程处理。并发和异步的思路是客户端的首要选择。

然而,当前所有的解决方案,不断的提高qps的方法,都是围绕着如何在用户空间使用当前内核提供的基础设施来完成。直到新浪开源了其fast socket。fast socket发现内核网络中的主要浪费在网络部分代码的锁。所以就为各个CPU单独创建了数据结构,形成无锁编程。据实验结果,nginx的效率可以提高100%以上。并且,既然可以做到同时accept多个队列(原来的只有一个,每次访问必须加锁),那么也就是说用户空间可以有多个工作进程同时accept而不会引起冲突。这也就同时解决了nginx的accept上锁的问题,很大程度上消除了大负载服务器的惊群问题。

还有一个比较好的解决方案是TCPCP,连接迁移技术。因为qps量大的的服务器一般会组成集群,但无论什么样的集群,如果只使用一台机器作为对外的统一接口都是必要的,但qps量大的时候,一台机器的服务也是会被打满的,那么如何充分的利用单台机器的处理能力呢?连接迁移这个内核技术可以让一个server单纯的处理tcp连接问题,而不用负责业务,从而极大的提高单台机器的qps。

TCP的安全问题

针对TCP协议本身,有很多安全问题。例如,协议规定,如果在监听时收到了SYN,必须要回复SYN/ACK。这就让攻击者可以通过发送SYN嗅探该端口是否打开。协议还规定,如果有人没有发送SYN。而是直接发送ACK,处于Listen的服务器应当返回RST,这也提供了一种嗅探方法。而也是协议的规定,server在回复SYNC/ACK的同时需要提供sequnce number,接下来的用户必须使用这个sequence number+1来作为其序号,这也为server提供了一种源地址验证的方法,可以有效的防止篡改源地址的dos攻击。

还有就是实现问题,linux在接收到一个syn请求的时候就会立即为这个请求分配内存等资源,这就是tcp洪泛的核心思想:耗尽server的可用资源。Linux在内核中有实现一种syn cookie,通过计算一个sequnce number做源地址验证,但是还是要在服务器端分配资源(也有方案TCPCP等可以让收到syn时不分配资源,但是CPU负担太重),并且计算sequnce number的方式决定了其CPU负担过重,在解决syn的同时引进了另外的攻击方式。就是不断的发送ack,让服务器忙于其计算和验证sequnce number(因为server是不存储针对某个IP连接其产生的sequnce number的,否则又是资源开销,是要根据client的ack的sequnce number中数字推导出来这个sequnce number是否正确,这个推导的过程就是一个性能短板)

还有一个比较严重的问题是FIN和RST,中间人通过伪造这两个包就可以实现断开操作。由于这种断开可以让server产生很多的CLOSE_WAIT和TIME_WAIT的socket,而这种状态的socket又没有特别好的处理方法,直到占满了可用的socket总数,造成资源的耗尽。但是这两种都是在连接建立后的攻击,难度上要求sequnce number必须要落在窗口的范围内,但是随着网速的发展,这个窗口越来越大,猜测越来越容易,所以网速的提高实际上是增加了这种攻击的可行性。我们看到这都是TCP协议本身的问题,在设计的过程中没有过多的考虑安全问题。相当长时间内,TCP还是会被不断的使用和改进,因为他的存在已经绑架了全球的技术工作者,替换的代价高昂。

总结

从上文的分析可以看出来,用户空间几乎可以做到在内核提供的机制上最充分的利用,但是内核空间的代码确实是进步速度跟不上用户空间。这无疑与内核代码的难以切入有关,这极大阻碍了内核的发展速度。但是退一步讲,内核这种基础设施,不能有了先进的思想就在内核中实验,相反的,应该在用户空间已经充分证明了其有效性,并且提出了迫切的需求的时候,内核才应该加以实现。所以内核的高门槛也是有道理的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: