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三次握手1.png

###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