TCP協議抓包分析

1 環境搭建

  建議閱讀此文前先了解TCP的原理。此文章僅爲了加深對TCP的理解。
  爲了在抓包過程中捕獲儘可能多種類的TCP報文,本文需要自己編寫java socket程序,並安裝Wireshark配套軟件。
  爲了方便理解TCP傳輸過程,僅客戶端向服務端發送數據。

1.1 編寫java程序

  程序中需要注意的幾點:

  1. 客戶端發送數據,服務端接受數據。將服務端buffer大小設置的明顯小於客戶端,是爲了捕獲流量控制報文。
  2. 客戶端發送的數據(即d://bb.jpg),應該選擇合適的大小。本文中bb.jpg大小爲7M(推薦),這是爲了捕獲足夠多的樣本來進行分析。
  3. 服務端中並沒有關閉socket,這是爲了捕獲reset報文。
/**
 * 服務端
 * 
 * @author youngaoo
 * @created 2018年5月16日上午11:09:51
 */
public class Server {

    public static void main(String[] args) {
        Server s = new Server();
        s.doServer();
    }

    public void doServer() {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(8081));
            SocketChannel socketChannel = serverSocketChannel.accept();

            ByteBuffer dst = ByteBuffer.allocate(1024);

            FileOutputStream fileOut = new FileOutputStream("d://cc.jpg");
            FileChannel fileChannel = fileOut.getChannel();

            int len = 0;
            while ((len = socketChannel.read(dst)) != -1) {
                dst.flip();
                fileChannel.write(dst);
                dst.clear();
            }
            fileOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

/**
 * 客戶端
 * 
 * @author youngaoo
 * @created 2018年5月15日下午12:06:15
 */
public class Client {

    public static void main(String[] args) {
        Client c = new Client();
        c.doClent();
    }

    public void doClent() {
        try {
            FileInputStream fileInputStream = new FileInputStream("d://bb.jpg");
            FileChannel fileChannel = fileInputStream.getChannel();
            ByteBuffer dst = ByteBuffer.allocate(1024*10);

            SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8081));

            int len = 0;
            while ((len = fileChannel.read(dst)) != -1) {
                dst.flip();
                socketChannel.write(dst);
                dst.clear();
            }
            fileInputStream.close();
            socketChannel.close();

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.2 安裝並配置Wireshark

  安裝和Wireshark和npcap。並打開Wireshark,做如下配置:

  1. 網卡選擇本地迴環
  2. 過濾器填寫port 8081 and host 127.0.0.1
    這裏寫圖片描述


2 抓包並分析

  開啓Wireshark抓包功能,然後依次運行服務端和客戶端,得到tcp報文段,如圖(圖中報文段並不完整,省略了多個意義重複的報文段):
這裏寫圖片描述
  可以看到,圖中包含了多種報文類型。編號爲1的紅框爲三次握手過程;編號爲3的代表“二次揮手”reset報文段。
  圖中凡是帶有中括號[]的文字,均是Wireshark添加的註釋,並不是TCP協議的內容。可以看到傳輸數據過程中,發生了多次流量控制,具體體現在[TCP Window Update]報文和編號爲②的報文段上。
  下面,將會詳細分析幾種報文的含義,和出現的原因。

2.1 TCP協議首部格式

  TCP協議的幾乎所有功能都與首部相關,因此這裏放上此圖,供後文說明使用。
這裏寫圖片描述

2.2 三次握手

  編號爲①的紅框爲三次握手的過程。在這個階段,通信雙方通過協商,得出了一系列信息,包括初始序列號,自己的接收窗口大小,最大報文段長度MSS,窗口擴大選項WS,選擇確認SACK。三次握手完成以後,通信雙方就可以根據這些信息,分別構建出自己發送窗口,MSS等。此時,一條邏輯上的連接就建立成功了。
這裏寫圖片描述
  紅框中的內容,就對應TCP首部的各個部分。同樣,使用中括號[]括起來的文字是Wireshark添加的,便於使用者理解。

2.3 傳送數據

  三次握手下面緊接着,就是傳送數據的報文。31306 → 8081 [ACK] Seq=1 Ack=1 Win=8192 Len=1460。其中Seq代表本報文段發送數據的第一個字節的序號;Win代表發送本報文段一方的接收窗口大小,在這裏,即代表客戶端的大小;Len即發送的數據的長度(單位爲字節);當建立連接完畢以後,所發送的所有報文段,Ack字段都必須爲1。
  該報文段發送的數據長度(Len),受到MSS值控制。之所以要協商MSS值,是因爲從網絡利用率來考慮。

2.4 流量控制

  流量控制發生在接收方來不及處理數據時,接收方要求發送發降低發送速率,即減小發送方的發送窗口大小。在第一個[Tcp Window Update]報文和其前一個報文,即流量控制報文。前一個報文將Win改爲768,即告訴客戶端,我的接收窗口爲768個字節,你應該據此修改自己的發送窗口。後面的[Tcp Window Update]報文又將自己的接受窗口增大至4096字節。
  最壞的情況是服務端的應用程序讀取數據的速度太慢,導致接受緩存達到最大容量,使接收窗口變爲0。那麼此時服務端就應該發送流量控制報文,[TCP ZeroWindow] 8081 → 31306 [ACK] Seq=1 Ack=31421 Win=0 Len=0,將Win設置爲0,通知客戶端不要再發送報文段了,等我處理完積壓數據再通知你。注意,在[TCP ZeroWindow]報文前,發送了一個[TCP Window Full]報文。此報文是客戶端根據服務端的接收窗口大小,MSS值計算出來的。計算方法爲:5120/1460=3…740。
  當服務器處理完畢積壓數據,又有了一些緩存空間,於是發送Win=3584的報文段。在Wireshark中,凡是擴大窗口的報文段都被註釋爲[Tcp Window Update]。

2.5 RESET報文

  編號爲③的報文段中,前兩個是四次揮手的前兩次揮手,此時客戶端到服務端的單向連接已經被關閉。接着應該進行服務端到客戶端連接的關閉。但是服務端的應用程序並沒有向TCP發送close命令,當應用程序進程結束後,操作系統的TCP就向客戶端發送了reset報文。

2.6 PSH報文

  發生在TCP層清空緩存時,纔將push置爲1。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章