tcpip 网络连接那些事

1、TCP协议

1.1 TCP状态迁移路线图


按照client/server两条路线讲述TCP状态迁移路线图
客户端TCP状态迁移:

1
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED

服务器TCP状态迁移:

1
CLOSED->LISTEN->SYN-RECEIVED->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED

1.2 三次握手

正常的三次握手

img

  1. 客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
  2. 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。
  3. 客户必须再次回应服务段一个ACK报文,这是报文段3

SERVER:

1
2
3
In [7]: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
In [8]: s.bind((HOST, PORT))
In [9]: s.listen(1)

CLIENT:

1
2
3
In [3]: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
In [4]: s.connect((host,port))
client调用connect,server调用了socket + bind + listen,三次握手建立成功

三次握手复位

image-20191119110247186

复现方式
SERVER:
没有监听10001端口,服务端会直接返回RSET

1
2
3
4
In [3]: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
In [4]: host=''
In [5]: port=10000
In [6]: s.bind((host,port)) ##没有listen

CLIENT:

1
2
In [5]: s.connect((host,port))
telnet 115.28.46.185 10001

可能的情况:到不存在的端口的连接请求
返回错误:telnet: Unable to connect to remote host: Connection refused

三次握手重传

image-20191119110345163

复现方式
SERVER:
iptables -I INPUT -p tcp –dport 10000 -j DROP #服务端DROP10000端口的数据包
CLIENT:
telnet 115.28.46.185 10000
可能的情况:丢失在发往Internet的途中,对端DROP掉数据包,应用无响应导致重传等
返回错误:telnet: Unable to connect to remote host: Connection timed out

1.3 四次挥手

四次挥手状态迁移一

img

  1. 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
  2. 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
  3. 服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
  4. 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

四次挥手状态迁移二

TCP协议的连接是全双工连接,一个TCP连接存在双向的读写通道。

简单说来是 “先关读,后关写”,一共需要四个阶段。以客户机发起关闭连接为例:

  1. 服务器读通道关闭
  2. 客户机写通道关闭
  3. 客户机读通道关闭
  4. 服务器写通道关闭,关闭行为是在发起方数据发送完毕之后,给对方发出一个FIN(finish)数据段。直到接收到对方发送的FIN,且对方收到了接收确认ACK之后,双方的数据通信完全结束,过程中每次接收都需要返回确认数据段ACK。

1.4 TCP状态

状态之SYN_RECV

服务端收到建立连接的SYN没有收到ACK包的时候处在SYN_RECV状态。
处在SYNC_RECV的TCP连接称为半连接,存储在内核的半连接队列中,在内核收到对端发送的ack包时会查找半连接队列,并将符合的requst_sock信息存储到完成三次握手的连接的队列中,然后删除此半连接。大量SYNC_RECV的TCP连接会导致半连接队列溢出,这样后续的连接建立请求会被内核直接丢弃,这就是SYN Flood攻击。
相关参数:

  1. net.ipv4.tcp_synack_retries
    对于syn请求,内核会发送SYN + ACK数据报文,对上一个SYN进行确认
    这里决定内核在放弃连接之前所送出的 SYN+ACK 数目
  2. net.ipv4.tcp_syncookies
    SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明。SYN Cookie是对TCP服务器端的三次握手协议作一些修改,专门用来防范SYN Flood攻击的一种手段。
    在TCP服务器收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值,在syn backlog队列不足的时候,提供一种机制临时将syn链接换出

状态之CLOSE_WAIT

发起TCP连接关闭的一方称为client,被动关闭的一方称为server。被动关闭的server收到FIN后,发出ACK的TCP状态之后状态变为CLOSE_WAIT, CLOSE_WAIT没有超时时间。
出现这种状况一般都是由于server端代码的问题,没有发出FIN,如果服务器上出现大量CLOSE_WAIT,应该要考虑检查代码。
CLIENT:
In [6]: s.close(),客户端调用了close()方法,进入FIN_WAIT2
tcp 0 0 115.28.46.185:45522 123.57.161.192:10000 FIN_WAIT2
SERVER:
SERVER端口进入CLOSE_WAIT状态,解决办法server调用close()方法
root@localhost:~# netstat -ant |grep 10000
tcp 0 0 0.0.0.0:10000 0.0.0.0:* LISTEN
tcp 1 0 123.57.161.192:10000 115.28.46.185:45522 CLOSE_WAIT

状态之FIN_WAIT2

FIN_WAIT2常见于存在客户端,客户端发出FIN之后收到服务端的ACK,但是没有收到服务端的FIN
client的连接过一段时间就没有了,而server的连接一直处于CLOSE_WAIT状态
原因在于Linux系统内核中有一个参数可以控制FIN_WAIT_2的时间:
net.ipv4.tcp_fin_timeout
抓包

1
2
13:27:48.635176 IP 115.28.46.185.45539 > 123.57.161.192.webmin: Flags [F.], seq 1, ack 1, win 29200, length 0
13:27:48.637587 IP 123.57.161.192.webmin > 115.28.46.185.45539: Flags [.], ack 2, win 29200, length 0

状态之FIN_WAIT1

FIN_WAIT1常见于存在客户端,客户端发出FIN之后没收收到服务端的ACK
SERVER:
In [13]: s.bind((host,port))
In [14]: s.listen(1)
CLIENT:
In [22]: s.connect ((host,port))
之后进入ESTABLISHED
SERVER:
root@localhost:~# iptables -I INPUT -s 115.28.46.185 -j DROP 配置DROP客户端的访问
CLIENT:
In [22]: s.close()
tcp 0 1 115.28.46.185:45534 123.57.161.192:10000 FIN_WAIT1
主要涉及两个参数
net.ipv4.tcp_max_orphans 最大孤儿socket
net.ipv4.tcp_orphan_retries 最大重试次数,负载比较高的主机可以设置小一些

状态之FIN_WAIT1抓包文件

状态之TIME_WAIT

2MSL(max segment lifetime)
TIME_WAIT是主动关闭连接的一方保持的状态,对于服务器来说它本身就是“客户端”,从而进入TIME_WAIT的状态,然后在保持这个状态2MSL(max segment lifetime)时间之后,彻底关闭回收资源。
这个是TCP/IP的设计者规定的,主要出于以下两个方面的考虑:

  1. 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
  2. 可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。

常见解决方案:
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭 net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭 net.ipv4.tcp_tw_recycle = 1
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间 #tcp_max_tw_buckets
这个是控制并发的TIME_WAIT的数量,默认值是180000,如果超限,那么,系统会把多的给destory掉,然后在日志里打一个警告(如:time wait bucket table overflow),官网文档说这个参数是用来对抗DDoS攻击的。也说的默认值180000并不小。这个还是需要根据实际情况考虑,增大会增加资源的占用。

查看tcp状态

linux查看tcp的状态命令:

1
2
3
4
1. netstat -nat  查看TCP各个状态的数量
2. sar -n SOCK 查看tcp创建的连接数
3. tcpdump -iany tcp port 10000 对tcp端口为10000的进行抓包,本地使用wireshark
4. netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’ 查看各连接状态数

2、HTTP协议

2.1 打开一个站点经过了什么?

以打开 www.aliyun.com 为例

  1. 将域名解析成ip地址
  2. 浏览器向web服务器发起Request
  3. 服务器向浏览器返回response

工具:chrome F12 火狐 firedebug
以及强大的工具Fiddler
Liunx下curl命令

2.2 使用Fiddler

请求第一行中的Method表示请求方法,比如”POST”,”GET”, Path-to-resoure表示请求的资源, Http/version-number 表示HTTP协议的版本号
响应HTTP/version-number表示HTTP协议的版本号, status-code 和message

URL(Uniform Resource Locator) 地址用于描述一个网络上的资源
schema://host[:port#]/path/…/[;url-params][?query-string] [#anchor]
scheme 指定低层使用的协议(例如:http, https, ftp)
host HTTP服务器的IP地址或者域名
port HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/
path 访问资源的路径
url-params
query-string 发送给http服务器的数据
anchor- 锚

2.3 GET、POST

GET和POST区别

  1. GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连
  2. POST主要通过该form表单的形式提交数据
  3. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制
  4. GET需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值

2.4 HEAD、OPTIONS

HEAD和OPTIONS

  1. HEAD只请求资源的首部,一个HEAD请求的响应中,HTTP头中包含的元信息应该和一个GET请求的响应消息相同。
  2. 跨域请求中会用到,它用于获取当前URL所支持的方法。若请求成功,则它会在HTTP头中包含一个名为“Allow”的头,会返回支持的方法,
    如截图,支持HEAD、GET、OPTIONS

2.5 状态码

Response 消息中的第一行叫做状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response.
HTTP/1.1中定义了5类状态码, 状态码由三位数字组成,第一个数字定义了响应的类别
1XX 提示信息 - 表示请求已被成功接收,继续处理
2XX 成功 - 表示请求已被成功接收,理解,接受
3XX 重定向 - 要完成请求必须进行更进一步的处理
4XX 客户端错误 - 请求有语法错误或请求无法实现
5XX 服务器端错误 - 服务器未能实现合法的请求

2.6 keep-alive和cookie

http协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对http服务器来说,它并不知道这两个请求来自同一个客户端。
Cookie是用来解决会话保持,slb的植入和重写cookie
HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,Keep-Alive不会永久保持连接,它有一个保持时间,可以在web服务里配置

2.7 Referer以及其他

Accept-Language:浏览器申明自己接收的语言
Accept-Encoding: gzip, deflate客户端支持gzip
X-cache 阿里cdn,hit是已经命中
http://www.w3.org/

-------------本文结束感谢您的阅读-------------