tcp三次握手流程简介
数据具体流程如下图
服务端调用listen函数变为LISTEN状态,客户端调用connect()函数向服务端发起SYN包
服务器接收到请求后,此时服务端变为SYN_RCVD状态,同时会把把该连接放入半连接队列里会,后回一个ACK,SYN包
客户端接收到SYN包后,会发送一个ACK包,服务器接收到ACK包后,连接建立成功,同时移如accept连接队列.
补充:建立半连接队列时syn攻击 解决:(net.ipv4.tcp_syncookies = 1),opt很多字段会被占用 syn队列满了不放半连接队列直接返回给客户端,客户端处理完发回cookies服务端解析tcp_syncookies后放accept队列
net.ipv4.tcp_syn_retries=6 #(三次握手重传次数) //后续实现
net.ipv4.tcp_synack_retries=6 #(三次握手重回次数) //后续实现
#半连接队列配置
syn半连接队列
net.ipv4.tcp max_syn_backlog = 1024
#全连接队列
accept队列
net.core.somaxconn = 1024 #操作系统最大
listent(fd,backlog) #if(backlog<=net.core.somaxconn) backlog生效
net.ipv4.tcp_abort_on_overflow = 0 #作用于全连接队列 1满了后 回rst 0则不处理 客户端收不到ack重发syn

###tcp具体包结构以及状态机 从rfc793 拷贝
#tcp包结构定义
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#完整状态机定义如下
+---------+ ---------\ active OPEN
| CLOSED | \ -----------
+---------+<---------\ \ create TCB
| ^ \ \ snd SYN
passive OPEN | | CLOSE \ \
------------ | | ---------- \ \
create TCB | | delete TCB \ \
V | \ \
+---------+ CLOSE | \
| LISTEN | ---------- | |
+---------+ delete TCB | |
rcv SYN | | SEND | |
----------- | | ------- | V
+---------+ snd SYN,ACK / \ snd SYN +---------+
| |<----------------- ------------------>| |
| SYN | rcv SYN | SYN |
| RCVD |<-----------------------------------------------| SENT |
| | snd ACK | |
| |------------------ -------------------| |
+---------+ rcv ACK of SYN \ / rcv SYN,ACK +---------+
| -------------- | | -----------
| x | | snd ACK
| V V
| CLOSE +---------+
| ------- | ESTAB |
| snd FIN +---------+
| CLOSE | | rcv FIN
V ------- | | -------
+---------+ snd FIN / \ snd ACK +---------+
| FIN |<----------------- ------------------>| CLOSE |
| WAIT-1 |------------------ | WAIT |
+---------+ rcv FIN \ +---------+
| rcv ACK of FIN ------- | CLOSE |
| -------------- snd ACK | ------- |
V x V snd FIN V
+---------+ +---------+ +---------+
|FINWAIT-2| | CLOSING | | LAST-ACK|
+---------+ +---------+ +---------+
| rcv ACK of FIN | rcv ACK of FIN |
| rcv FIN -------------- | Timeout=2MSL -------------- |
| ------- x V ------------ x V
\ snd ACK +---------+delete TCB +---------+
------------------------>|TIME WAIT|------------------>| CLOSED |
+---------+ +---------+
###三次握手代码实现
注意如果抓包出现TCP Retransmission,请仔细检查[SYN, ACK]是否正确
代码地址
具体流程
分了两个goroutine,一个收包,一个发包 1 判断是tcp的包后,解析完交给tcp状态机处理,状态机会根据4元组判断状态以及推动状态变更 2 需要发包后写入ring buf,由另一个goroutine去读取发包 3 进入状态机后,若第一次连接则建立一个新的状态机,否则根据之前代码的状态机结构进行状态变更 4 变更完状态后,根据是否需要发送包给客户端,放入写ring buf,由另一个goroutine去读取发包
//判断是tcp协议后 进入状态机
case layers.IPProtocolTCP:
tcpLayers, ok := gopacket.NewPacket(ip4.LayerPayload(), layers.LayerTypeTCP, gopacket.Default).Layer(layers.LayerTypeTCP).(*layers.TCP)
if !ok {
continue
}
log.Default().Printf("tcp ack:%v,syn:%v\n", tcpLayers.Ack, tcpLayers.SYN)
err = tcp.StateMachine(ctx, ip4.SrcIP, ip4.DstIP, tcpLayers)
if err != nil {
log.Default().Printf("tcp2 StateMachine err:%v", err)
}
}
//tcp状态定义
const (
GO_TCP_STATUS_CLOSED TcpStatus = iota
GO_TCP_STATUS_LISTEN
GO_TCP_STATUS_SYN_RCVD
GO_TCP_STATUS_SYN_SENT
GO_TCP_STATUS_ESTABLISHED
GO_TCP_STATUS_FIN_WAIT_1
GO_TCP_STATUS_FIN_WAIT_2
GO_TCP_STATUS_CLOSING
GO_TCP_STATUS_TIME_WAIT
GO_TCP_STATUS_CLOSE_WAIT
GO_TCP_STATUS_LAST_ACK
)
//状态机对应结构
type Tcb struct {
//todo fd 改成io.Reader
fd int
SrcIP, DstIP net.IP
SrcPort, DstPort layers.TCPPort
tcpStatus TcpStatus
SendBuf, RecBuf *container.Ring
Next, Prev *Tcb
Seq, Ack uint32
}
//状态及链表存储
type tcbTable struct {
tcbHead *Tcb
count int
}
具体测试步骤(目前由于只有一个连接,暂未解决race,后续解决)
#窗口1
nc 192.168.2.1 8004
#窗口2
sudo tshark -i tun0 -f "tcp"
Running as user "root" and group "root". This could be dangerous.
Capturing on 'tun0'
1 0.000000000 192.168.2.0 → 192.168.2.1 TCP 60 47080 → 8004 [SYN] Seq=0 Win=64416 Len=0 MSS=1464 SACK_PERM=1 TSval=3612825133 TSecr=0 WS=128
2 0.002529910 192.168.2.1 → 192.168.2.0 TCP 40 8004 → 47080 [SYN, ACK] Seq=0 Ack=1 Win=1504 Len=0
3 0.002562878 192.168.2.0 → 192.168.2.1 TCP 40 47080 → 8004 [ACK] Seq=1 Ack=1 Win=64416 Len=0