TCP頭部和IPV4頭部除了固定的20字節外,都設置了 OPTION 字段用於存儲自定義的數據,由於TCP頭部和IPV4的報文長度字段均爲4字節,所表示的最大值爲15, 乘4,報文頭部最大長度爲60字節,所以Option字段最大長度爲40字節,足夠存儲大量的報文控制信息。TCP和IPV4 OPTION的格式均爲(標識字段 - 長度 - 數據)格式,通常採起4字節對齊存儲。linux
目前 IP Option應用場景較少,且公網路由器對 IP Option的檢查較爲嚴格,通常都會直接丟棄帶有 IP Option 的報文。TCP Option 的應用場景則較爲普遍,常見的包括 TimeStamp(應用於時延測量), TCP_Window_Scalling(長肥網絡下,TCP接收窗口須要足夠大才能達到瓶頸帶寬,此時須要 Window_Scaling 來表示一個更大的接收窗口),TCP_SACK(選擇性確認,能夠大幅提升TCP在丟包時的性能)等等,這些選項通常都會默認開啓,路由器、端主機對這些選項的支持度也較高。本文主要介紹 TCP & IPV4 Option的處理邏輯,而後介紹一種經過 IP Option 字段在內網傳輸報文控制數據的方法。網絡
步驟1: 構造TCP Option,計算存儲空間socket
Linux把TCP選項的處理邏輯分爲了SYN報文的選項和普通報文的選項兩個部分,在TCP報文構造函數 static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask) 中有下面這段邏輯tcp
步驟2:分配 TCP 頭部須要的存儲空間,包括20個字節的標準頭部加上TCP Option的部分。ide
tcp_options_size 爲TCP選項部分的總長度,tcp_transmit_skb 接下來在 skb_push 中爲TCP頭部分配存儲空間。
函數
步驟3 : 向TCP頭部寫入構造好的TCP Option。 性能
tcp_transmit_skb 接下來經過 tcp_options_write() 函數把構造好的TCP Option 從 opts 中讀出並寫入TCP報文首部ui
步驟4 : TCP Option 的構造邏輯完成,報文進入IP層的處理邏輯。spa
步驟5:TCP Option 的解析在 tcp_parse_options() 函數 中完成,協議棧在接收到TCP報文後,會調用該函數完成報文頭部的OPTION字段的解析。3d
IP Option的構造與TCP Option相似。
步驟1:分配存儲空間,在IP報文構造函數 ip_queue_xmit 中,有下面這一段邏輯,分配IP報文頭部空間和OPTION字段的存儲空間
步驟2 : 構造OPTION字段,具體邏輯在 ip_options_build 中,與 TCP Option的邏輯相似。
步驟3: 解析, IP Option的解析在 ip_options_compile 中完成
在咱們的應用場景下,例如向路由器通告一些信息等,報文除了傳輸數據外,還要傳輸一些控制信息,咱們就是經過IP報頭的OPTION字段來攜帶這些控制信息。通常狀況下,帶有TCP選項的報文,即便TCP選項是自定義的,公網路由器也不會輕易丟包,但公網路由器對IP選項的審查要嚴格的多,帶有IP選項的報文。當報文從局域網轉發到公網的時候,Linux網關能夠在Qdisc中刪除該選項,並轉發到公網,Linux上發送一個帶有自定義IP選項的報文也很是容易,不須要修改內核,只須要在用戶態用 setsockopt() 調用即可以完成,下面是一個設置自定義IP 選項的Linux套接字客戶端程序示例。
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 #include <netinet/ip.h> 8 9 #define SERV_PORT 1234 10 #define SERV_IP "127.0.0.1" 11 #define MAXLINE 4096 12 #define MAXSIZE 40 13 14 #define IPOPT_TAG 0x21 //IP選項標誌字段 15 #define IPOPT_LEN 8 //IP選項長度字段 16 17 int main(int argc,char *argv[]) 18 { 19 int sockfd; 20 struct sockaddr_in servaddr; 21 22 memset(&servaddr,0,sizeof(servaddr)); 23 servaddr.sin_family = AF_INET; 24 servaddr.sin_addr.s_addr = inet_addr(SERV_IP); 25 servaddr.sin_port = htons(SERV_PORT); 26 27 //構造自定義的TCP選項 28 unsigned char opt[MAXSIZE]; 29 opt[0] = IPOPT_TAG; 30 opt[1] = IPOPT_LEN; 31 //寫入選項數據 32 *(int *)(opt + 4) = htonl(50000); 33 34 if((sockfd = socket(AF_INET,SOCK_STREAM,0)) <= 0){ 35 perror("socket error : "); 36 exit(1); 37 } 38 39 if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){ 40 perror("connect error "); 41 exit(1); 42 } 43 44 //設置套接字發送該選項 45 if(setsockopt(sockfd,IPPROTO_IP,IP_OPTIONS,(void *)opt,IPOPT_LEN) < 0){ 46 perror("setsockopt error "); 47 exit(1); 48 } 49 50 char buff[MAXLINE]; 51 52 while(fgets(buff,MAXLINE,stdin) != NULL){ 53 if(write(sockfd,buff,strlen(buff)) < strlen(buff)){ 54 perror("write error "); 55 exit(1); 56 } 57 } 58 59 close(sockfd); 60 }
內核並無檢測 setsockopt() 的參數,直接將自定義的選項複製到了IP報文選項部分。
咱們能夠經過 getsockopt() 函數能夠直接讀取自定義的IP OPTION。
TCP自定義選項的設置和讀取相對於IP選項要麻煩一些,通常向TimeStamp,SACK等TCP選項並不須要用戶去讀取,所以也沒有開放用戶層訪問的接口,直接經過 getsockopt() 函數沒法讀取自定義的TCP 選項,但咱們能夠經過修改內核 getsockopt() 來實現自定義TCP選項的讀取,咱們知道內核 getsockopt() 函數底層是由 do_ip_getsockopt 和 do_tcp_getsockopt 等協議相關的接口組成的, 在 do_tcp_getsockopt 接口內咱們能夠添加用戶層訪問自定義TCP選項的接口,而後即可以在用戶層經過 getsockopt() 函數來訪問自定義的 TCP 選項了。
TCP Option字段對於提高TCP性能有較大意義,所以須要瞭解常見的TCP Option字段的含義、開啓和關閉。IP Option字段通常來講不容易遇見,但在一些特殊的應用場景下,例如在局域網內捎帶報文控制數據仍是頗有用處的。
Linux Kernel 4.12.13 https://elixir.bootlin.com/linux/v4.12.13/source