tcp

tcp TCP将数据分解成网络数据包,并在每个数据包中添加少量数据。这些附加数据包括一个序列号,用于检测丢失或到达顺序不正确的数据包,以及一个校验和,可以检测数据包数据内的错误。当其中任何一个问题发生时,TCP使用自动重传请求 (ARQ)告诉发送方重新发送丢失或损坏的数据包。 https://luoguochun.cn/post/2016-09-23-tcp-fuck/ TCP 的连接标识是通过 “源IP + 源Port + 目标IP + 目标Port + 协议号“ 组成的唯一五元组,一旦其中一个参数发生变化,则需要重新创建新的 TCP 连接。 tcp协议是一个比较复杂的协议,对tcp协议深入理解的,真的非常少非常少;对tcp协议误理解或理解片面的,真的非常多非常多。当然这也包括自己在内,当然也可能包括这篇小结在内。 P.S.: 《TCP/IP详解卷1:协议》是介绍TCP/IP协议栈最经典的著作(神级已故人物W.Richard Stevens经典书籍之一),然而个人觉得这个"详解"对于tcp的介绍有点简略或者理解起来印象非深,读了一次,一次又一次,还是概念模糊。当然这也与中文译本烂得一塌糊涂有关。同时这本经典书籍也有了它的更新版,不同的是作者已经不是原来的神级人物,相同的是译文继续烂。 tcp协议头 tcp协议头 tcp基本协议头占用20个字节,协议中Header Length(4bits)中标明协议头的长度,含义是多少个32bit数据,该字段占用4位,所有整个tcp头最多可以占用60字节。当tcp建立时,主机会生成一个初始的序列号(ISN, Initial Sequence Number),在tcpdump程序抓取的报文中可以看到该初始Sequence,Sequence的生成方式有一定的算法,一般tcp分析很少关注。如果tcpdump查看报文,可以发现,第一个SYN包收到ACK后,后续的SEQ都变成了ISN的偏移量。如果是用大鲨鱼wireshark查看报文,则可以发现,seq总是从0开始,并提示这个值是相对值,大鲨鱼已经处理好这些细节。如: tcpdump seq wireshark seq tcp报文SYN ACK的计算如下: A -> B SYN J ACK K LEN L B -> A SYN K ACK J+L LEN M A -> B SYN J+L ACK K+M 需要注意到的是,注意,对于DATA LEN为0的,发送的SYN包和FIN包,需要消耗一个序号。为了提高传送的效率,ACK是支持累计的,也就是说没必要对每个SYN进行ACK。如: 发送端连续发送3个报文,那么接收端收到3个报文后,可以直接应答一个ACK。 tcp标志位 CWR(Congestion Window Reduced) & ECN (ECN-Echo, Explicit Congestion Notification) CWR 阻塞窗口已减少,意思是告诉对端我已经按照你的要求,进行阻塞窗口减少了,并启动阻塞算法来控制我的发包速度; ECN 显式阻塞窗口通知,意思通知发送方,我接收的报文出现了阻塞,请控制发包速度。也就是说,CWR 和 ECN 必须配合使用,CWR 是收到 ECN 的应答。此外,在tcp三次握手时,这两个标志表明tcp端是否支持ECN。如果建立连接一方支持,则在发送的SYN包,将 ECN 标志置为1,如果服务端也支持,则在ACK包只设置ECN。缘由: tcp建立连接后,报文经过经过路由或网关等网络设备后,在路由器或网关等网络设备出现阻塞时,路由器或网关等设备设置IP层的某个标志表明出现阻塞,这样接收可以明确知道报文出现了阻塞。然而,需要知道阻塞进行阻塞控制的是报文发送方而非接收方。所以接收方会在ACK报文中设置ECN标志,同时发送方在ACK中设置CWR标志,表明已经收到ECN,并进行了减少阻塞窗口操作和启用阻塞算法。 ...

2021-02-15 · 5 min · 1064 words · -

tcp reset, rst

tcp reset, rst 在谈 RST 攻击前,必须先了解 TCP: 如何通过三次握手建立 TCP 连接、四次握手怎样把全双工的连接关闭掉、滑动窗口是怎么传输数据的、TCP 的 flag 标志位里 RST 在哪些情况下出现。下面我会画一些尽量简化的图来表达清楚上述几点,之后再了解下 RST 攻击是怎么回事。 TCP是什么? TCP 是在 IP 网络层之上的传输层协议,用于提供 port 到 port 面向连接的可靠的字节流传输。我来用土语解释下上面的几个关键字: port到port: IP层只管数据包从一个IP到另一个IP的传输,IP层之上的TCP层加上端口后,就是面向进程了,每个port都可以对应到用户进程。 可靠: TCP会负责维护实际上子虚乌有的连接概念,包括收包后的确认包、丢包后的重发等来保证可靠性。由于带宽和不同机器处理能力的不同,TCP要能控制流量。 字节流: TCP会把应用进程传来的字节流数据切割成许多个数据包,在网络上发送。IP包是会失去顺序或者产生重复的,TCP协议要能还原到字节流本来面目。 从上面我用PowerPoint画的TCP协议图可以看到,标志位共有六个,其中RST位就在TCP异常时出现,也是我这篇文章重点关注的地方。 通过三次握手建立连接 下面我通过A向B建立TCP连接来说明三次握手怎么完成的。 为了能够说清楚下面的RST攻击,需要结合上图说说: SYN标志位、序号、滑动窗口大小。 建立连接的请求中,标志位SYN都要置为1,在这种请求中会告知MSS段大小,就是本机希望接收TCP包的最大大小。 发送的数据TCP包都有一个序号。它是这么得来的: 最初发送SYN时,有一个初始序号,根据RFC的定义,各个操作系统的实现都是与系统时间相关的。之后,序号的值会不断的增加,比如原来的序号是100,如果这个TCP包的数据有10个字节,那么下次的TCP包序号会变成110。 滑动窗口用于加速传输,比如发了一个seq=100的包,理应收到这个包的确认ack=101后再继续发下一个包,但有了滑动窗口,只要新包的seq与没有得到确认的最小seq之差小于滑动窗口大小,就可以继续发。 滑动窗口 滑动窗口毫无疑问是用来加速数据传输的。TCP要保证"可靠",就需要对一个数据包进行ack确认表示接收端收到。有了滑动窗口,接收端就可以等收到许多包后只发一个ack包,确认之前已经收到过的多个数据包。有了滑动窗口,发送端在发送完一个数据包后不用等待它的ack,在滑动窗口大小内可以继续发送其他数据包。举个例子来看吧。 大家看上图,标志位为.表示所有的flag都为0。标志位P表示flag为PSH的TCP包,用于快速传输数据。 前三个包是三次握手,客户端表示自己的滑动窗口大小是65535 (我的XP机器) ,服务器端表示滑动窗口是5840 (屏幕宽了,没截出来) 。从第四个包开始,客户端向服务器发送PSH包,数据长度是520字节,服务器发了ack确认包。注意此时win窗口大小发生了改变哈。以此类推。 倒数第二、三包,服务器在滑动窗口内连续向客户端发包,客户端发送的ack 124同时确认了之前的两个包。这就是滑动窗口的功能了。 如果谈到TCP攻击就需要注意,TCP的各种实现中,在滑动窗口之外的seq会被扔掉!下面会讲这个问题。 四次握手的正常TCP连接关闭 先画张简单的正常关闭连接状态变迁图。 FIN标志位也看到了,它用来表示正常关闭连接。图的左边是主动关闭连接方,右边是被动关闭连接方,用netstat命令可以看到标出的连接状态。 FIN是正常关闭,它会根据缓冲区的顺序来发的,就是说缓冲区FIN之前的包都发出去后再发FIN包,这与RST不同。 RST 标志位 RST 表示复位,用来异常的关闭连接,在 TCP 的设计中它是不可或缺的。就像上面说的一样,发送 RST 包关闭连接时,不必等缓冲区的包都发出去 (不像上面的 FIN 包) ,直接丢弃缓存区的包发送 RST 包。而接收端收到 RST 包后,也不必发送 ACK 包来确认。 ...

2020-04-16 · 5 min · 877 words · -

TCP_NODELAY, TCP_CORK, Nagle

TCP_NODELAY, TCP_CORK, Nagle TCP/IP之 Nagle 算法与40ms延迟提到了Nagle 算法。这样虽然提高了网络吞吐量, 但是实时性却降低了, 在一些交互性很强的应用程序来说是不允许的, 使用 TCP_NODELAY 选项可以禁止 Nagle 算法。 禁止Nagle 后应用程序向内核递交的每个数据包都会立即发送出去。 但是禁止 Nagle, 网络传输仍然受到 TCP 确认延迟机制的影响。 TCP_CORK CORK 意思是塞子, TCP中的 CORK 意思是将连接塞住, 使得数据先不发出去, 等到拔去塞子后再发出去。 设置该选项后, 内核会尽力把小数据包拼接成一个大的数据包 (一个MTU) 再发送出去, 一定时间后, 内核仍然没有组合成一个 MTU 时也必须发送现有的数据。 然而, TCP_CORK 的实现可能并不像你想象的那么完美, CORK 并不会将连接完全塞住。内核其实并不知道应用层到底什么时候会发送第二批数据用于和第一批数据拼接以达到 MTU 的大小, 因此内核会给出一个时间限制, 在该时间内没有拼接成一个大包 (努力接近 MTU) 的话, 内核就会无条件发送。 也就是说若应用层程序发送小包数据的间隔不够短时, TCP_CORK 就没有一点作用, 反而失去了数据的实时性 (每个小包数据都会延时一定时间再发送,这个时间超过了内核的时间限制) 。 Nagle 算法和 CORK 算法非常类似, 但是它们的着眼点不一样, Nagle 算法主要避免网络因为太多的小包 (协议头的比例非常之大) 而拥塞, 而 CORK 算法则是为了提高网络的利用率,使得总体上协议头占用的比例尽可能的小。如此看来这二者在避免发送小包上是一致的,在用户控制的层面上,Nagle算法完全不受用户socket的控制,你只能简单的设置TCP_NODELAY而禁用它,CORK算法同样也是通过设置或者清除TCP_CORK使能或者禁用之,然而Nagle算法关心的是网络拥塞问题,只要所有的ACK回来则发包,而CORK算法却可以关心内容,在前后数据包发送间隔很短的前提下 (很重要,否则内核会帮你将分散的包发出) ,即使你是分散发送多个小数据包,你也可以通过使能CORK算法将这些内容拼接在一个包内,如果此时用Nagle算法的话,则可能做不到这一点。 Nagle 算法 根据创建者John Nagle命名。该算法用于对缓冲区内的一定数量的消息进行自动连接。该处理过程(称为 Nagling ), 通过减少必须发送的封包的数量, 提高了网络应用 程序系统的效率。Nagle算法, 由Ford Aerospace And Communications Corporation Congestion Control in IP/TCPinternetworks(IETF RFC 896)(1984)定义, 最初是用于缓冲 Ford 的私有 TCP/IP 网络拥塞情况, 不过被广泛传播开来。 ...

2017-09-21 · 2 min · 272 words · -

golang tcp socket

golang tcp socket Golang的主要 设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端 程序必不可少也是至关重要的一部分。在日常应用中,我们也可以看到Go中的net以及其subdirectories下的包均是"高频+刚需",而TCP socket则是网络编程的主流,即便您没有直接使用到net中有关TCP Socket方面的接口,但net/http总是用到了吧,http底层依旧是用tcp socket实现的。 网络编程方面,我们最常用的就是tcp socket编程了,在posix标准出来后,socket在各大主流OS平台上都得到了很好的支持。关于tcp programming,最好的资料莫过于W. Richard Stevens 的网络编程圣经《UNIX网络 编程 卷1: socket 联网API》 了,书中关于tcp socket接口的各种使用、行为模式、异常处理讲解的十分细致。Go是自带runtime的跨平台编程语言,Go中暴露给语言使用者的tcp socket api是建立OS原生tcp socket接口之上的。由于Go runtime调度的需要,golang tcp socket接口在行为特点与异常处理方面与OS原生接口有着一些差别。这篇博文的目标就是整理出关于Go tcp socket在各个场景下的使用方法、行为特点以及注意事项。 模型 从tcp socket 诞生后, 网络编程架构模型也几经演化, 大致是: “每进程一个连接” –> “每线程一个连接” –> “Non-Block + I/O多路复用 (linux epoll/windows iocp/freebsd darwin kqueue/solaris Event Port)"。伴随着模型的演化,服务程序愈加强大,可以支持更多的连接,获得更好的处理性能。 目前主流web server一般均采用的都是"Non-Block + I/O多路复用” (有的也结合了多线程、多进程) 。不过I/O多路复用也给使用者带来了不小的复杂度,以至于后续出现了许多高性能的I/O多路复用框架, 比如libevent、libev、libuv等,以帮助开发者简化开发复杂性,降低心智负担。不过Go的设计者似乎认为I/O多路复用的这种通过回调机制割裂控制流的方式依旧复杂,且有悖于"一般逻辑"设计,为此Go语言将该"复杂性"隐藏在Runtime中了: Go开发者无需关注socket是否是 non-block的,也无需亲自注册文件描述符的回调,只需在每个连接对应的goroutine中以"block I/O"的方式对待socket处理即可,这可以说大大降低了开发人员的心智负担。一个典型的Go server端程序大致如下: //go-tcpsock/server.go func handleConn(c net.Conn) { defer c.Close() for { // read from the connection ...

2016-06-29 · 11 min · 2233 words · -

TCP 粘包 拆包

TCP 粘(nián)包 拆包 这两个词并没有一一对应的英文 tcp 文档中并不存在 粘包拆包的描述, 一般粘包抓包是指应用层协议的边界定义和数据报读取/处理的问题 tcp是面向流的协议, 在tcp上接收数据报(datagram) 就需要处理 流(stream) 到 数据报的过程. https://www.zhihu.com/question/20210025 TCP是面向字节流的协议,就是没有界限的一串数据,本没有“包”的概念,“粘包”和“拆包”一说是为了有助于形象地理解这两种现象。 粘包拆包发生场景 因为TCP是面向流,没有边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。 如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。 如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。 正常的理想情况,两个包恰好满足TCP缓冲区的大小或达到TCP等待时长,分别发送两个包; 粘包:两个包较小,间隔时间短,发生粘包,合并成一个包发送; 拆包:一个包过大,超过缓存区大小,拆分成两个或多个包发送; 拆包和粘包:Packet1过大,进行了拆包处理,而拆出去的一部分又与Packet2进行粘包处理。 粘包 TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。 TCP网络通信时候会发生粘包/拆包的问题,接下来探讨其解决之道。 什么是粘包/拆包 一般所谓的 TCP 粘包是在一次接收数据不能完全地体现一个完整的消息数据。 TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上 MTU 的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。 处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。 情况分析 TCP 粘包通常在流传输中出现,UDP 则不会出现粘包,因为 UDP 有消息边界,发送数据段需要等待缓冲区满了才将数据发送出去,当满的时候有可能不是一条消息而是几条消息合并在换中去内,在成粘包;另外接收数据端没能及时接收缓冲区的包,造成了缓冲区多包合并接收,也是粘包。 解决办法 消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。 包尾添加特殊分隔符,例如每条报文结束都添加回车换行符 (例如FTP协议) 或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。例如,FTP协议; 将消息分为消息头和消息体,消息头中包含表示信息的总长度 (或者消息体长度) 的字段 更复杂的自定义应用层协议 http://my.oschina.net/imhoodoo/blog/357290 为什么UDP没有粘包? 粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。 https://cloud.tencent.com/developer/article/1804413 Netty对粘包和拆包问题的处理 Netty对解决粘包和拆包的方案做了抽象,提供了一些解码器 (Decoder)来解决粘包和拆包的问题。如: LineBasedFrameDecoder:以行为单位进行数据包的解码; DelimiterBasedFrameDecoder:以特殊的符号作为分隔来进行数据包的解码; FixedLengthFrameDecoder:以固定长度进行数据包的解码; LenghtFieldBasedFrameDecode:适用于消息头包含消息长度的协议 (最常用); 基于Netty进行网络读写的程序,可以直接使用这些Decoder来完成数据包的解码。对于高并发、大流量的系统来说,每个数据包都不应该传输多余的数据 (所以补齐的方式不可取),LenghtFieldBasedFrameDecode更适合这样的场景。 小结 TCP协议粘包拆包问题是因为TCP协议数据传输是基于字节流的,它不包含消息、数据包等概念,需要应用层协议自己设计消息的边界,即消息帧 (Message Framing)。如果应用层协议没有使用基于长度或者基于终结符息边界等方式进行处理,则会导致多个消息的粘包和拆包。 虽然很多框架中都有现成的解决方案,比如Netty,但底层的原理我们还是要清楚的,而且还要知道有这么会事,才能更好的结合场景进行使用。

2015-09-17 · 1 min · 69 words · -

TCP Keep-Alives

TCP Keep-Alives https://zhuanlan.zhihu.com/p/28894266 https://datatracker.ietf.org/doc/html/rfc1122#page-101 当客户端端等待超过一定时间后自动给服务端发送一个空的报文,如果对方回复了这个报文证明连接还存活着,如果对方没有报文返回且进行了多次尝试都是一样,那么就认为连接已经丢失,客户端就没必要继续保持连接了。如果没有这种机制就会有很多空闲的连接占用着系统资源。 KeepAlive并不是TCP协议规范的一部分,但在几乎所有的TCP/IP协议栈(不管是Linux还是Windows)中,都实现了KeepAlive功能。 如何设置它? 在设置之前我们先来看看KeepAlive都支持哪些设置项 KeepAlive默认情况下是关闭的,可以被上层应用开启和关闭 tcp_keepalive_time: KeepAlive的空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2小时) tcp_keepalive_intvl: KeepAlive探测包的发送间隔,默认值为75s tcp_keepalive_probes: 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次) 在Linux内核设置 KeepAlive默认不是开启的,如果想使用KeepAlive,需要在你的应用中设置SO_KEEPALIVE才可以生效。 查看当前的配置: cat /proc/sys/net/ipv4/tcp_keepalive_time cat /proc/sys/net/ipv4/tcp_keepalive_intvl cat /proc/sys/net/ipv4/tcp_keepalive_probes 在Linux中我们可以通过修改 /etc/sysctl.conf 的全局配置: net.ipv4.tcp_keepalive_time=7200 net.ipv4.tcp_keepalive_intvl=75 net.ipv4.tcp_keepalive_probes=9 添加上面的配置后输入 sysctl -p 使其生效,你可以使用 sysctl -a | grep keepalive 命令来查看当前的默认配置 如果应用中已经设置SO_KEEPALIVE,程序不用重启,内核直接生效

2013-10-20 · 1 min · 41 words · -