1. 程式人生 > >TCP協議關鍵資料結構

TCP協議關鍵資料結構

1、TCP協議頭資料結構

TCP協議頭資料結構是struct tcphdr,定義在include/linux/tcp.h中,主要包含源埠、目的埠、協議長度、控制標誌flags....

struct tcphdr {
	__be16	source;		//源埠
	__be16	dest;		//目的埠
	__be32	seq;			//資料段的起始序列號	
	__be32	ack_seq;		//確認序列號
#if defined(__LITTLE_ENDIAN_BITFIELD)   //小端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
	__u16	res1:4,
		doff:4,			//協議頭長度
		fin:1,			//斷開標誌
		syn:1,			
		rst:1,
		psh:1,
		ack:1,
		urg:1,
		ece:1,
		cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)    //大端
	__u16	doff:4,
		res1:4,
		cwr:1,
		ece:1,
		urg:1,
		ack:1,
		psh:1,
		rst:1,
		syn:1,
		fin:1;
#else
#error	"Adjust your <asm/byteorder.h> defines"
#endif	
	__be16	window;		//視窗控制
	__sum16	check;		//阻塞控制
	__be16	urg_ptr;		
};

2、TCP的控制緩衝區

TCP是完全非同步的協議,實際資料段的傳送獨立於來自所有套接字層的寫操作。TCP層分配的socket buffer來存放應用層寫入套接字的資料,但應用程式控制管理資料包的資訊存放在TCP控制緩衝區,TCP緩衝區由struct tcp_skb_cb資料結構描述,當資料從應用程式複製到TCP層的socket buffer時,函式需要TCP控制緩衝區中TCP協議頭的資訊來設定struct tcphdr資料結構中的相關資料域。

struct tcp_skb_cb {
	union {
		struct inet_skb_parm	h4;							//ip選項
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
		struct inet6_skb_parm	h6;
#endif
	} header;	/* For incoming frames		*/
	__u32		seq;		/* Starting sequence number	資料段起始序列號*/
	__u32		end_seq;	/* SEQ + FIN + SYN + datalen	最後一個數據段結束序列號*/
	__u32		when;		/* used to compute rtt's	*/
	__u8		flags;		/* TCP header flags.	TCP協議頭中的flags	*/

	/* NOTE: These must match up to the flags byte in a
	 *       real TCP header.
	 */
#define TCPCB_FLAG_FIN		0x01
#define TCPCB_FLAG_SYN		0x02
#define TCPCB_FLAG_RST		0x04
#define TCPCB_FLAG_PSH		0x08
#define TCPCB_FLAG_ACK		0x10
#define TCPCB_FLAG_URG		0x20
#define TCPCB_FLAG_ECE		0x40
#define TCPCB_FLAG_CWR		0x80

	__u8		sacked;		/* State flags for SACK/FACK.	前送回答/選擇回答的狀態標誌*/
#define TCPCB_SACKED_ACKED	0x01	/* SKB ACK'd by a SACK block	*/
#define TCPCB_SACKED_RETRANS	0x02	/* SKB retransmitted		*/
#define TCPCB_LOST		0x04	/* SKB is lost			*/
#define TCPCB_TAGBITS		0x07	/* All tag bits			*/

#define TCPCB_EVER_RETRANS	0x80	/* Ever retransmitted frame	*/
#define TCPCB_RETRANS		(TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)

	__u32		ack_seq;	/* Sequence number ACK'd	*/
};

關鍵元素詳解:

seq:輸出資料段的起始序列號。

end_seq:最後一個輸出資料段結束序列號,結束序列號的值等於最後傳送的資料段序列號 + 一個SYN資料段 + 一個FIN資料段 + 資料段長度,即 end_seq = seq + SYN + FIN + 當前資料段長度。

when:用於計算RTT(Round-trip time)值,即資料段在網路上傳送時間,when資料域用來管理資料段傳輸起始計時和資料重傳,TCP資料段傳送方根據when的值計算出資料後要等待多長時間,如果等待一定時間後仍沒有收到接受段回覆的ACK,就重傳資料段。

flags:與TCP協議頭中的flags資料域定義相同。

sacked:儲存了選擇回答(SACK:Selective Acknowledge)和前送回答(FACK:Forward Acknowledge)的狀態標誌,有效標誌如下:

有效的狀態標誌位
標誌 作用
TCPCB_SACKED_ACKED 1 SACK塊已經給出了skb資料緩衝區中的段回答資訊
TCPCB_SACKED_RESTRANS 2 資料段需要重傳
TCPCB_LOST 4 資料段丟失
TCPCB_TAGBITS   結合前面三個標誌來標識資料段
TCPCB_EVER_RETRANS 0X80 指明資料段以前是否重傳過
TCPCB_RETRANS   指明數段是一個重傳過的資料段

TCPCB_TAGBITS = TCPCB_SACKED_ACKED |  TCPCB_SACKED_RESTRANS | TCPCB_LOST

3、TCP套接字資料結構

TCP套接字資料結構struct tcp_sock包含了管理TCP協議各方面的資訊,如傳送和接受方的序列號、TCP視窗尺寸、避免網路阻塞等。struct sock資料結構很龐大,在include/linux/tcp.h檔案中定義。

(1)inet_conn:INET協議族面向連線的套接字結構體,定義在include/net/inet_connection_sock檔案中,其中包含了sturct inet_connection_sock_af_ops *icsk_af_ops資料結構,這個資料結構是套接字操作函式指標,比如getsockopt函式和setsockopt函式指標。

struct inet_connection_sock_af_ops {
	int	    (*queue_xmit)(struct sk_buff *skb);
	void	    (*send_check)(struct sock *sk, struct sk_buff *skb);
	int	    (*rebuild_header)(struct sock *sk);
	int	    (*conn_request)(struct sock *sk, struct sk_buff *skb);
	struct sock *(*syn_recv_sock)(struct sock *sk, struct sk_buff *skb,
				      struct request_sock *req,
				      struct dst_entry *dst);
	int	    (*remember_stamp)(struct sock *sk);
	u16	    net_header_len;
	u16	    sockaddr_len;
	int	    (*setsockopt)(struct sock *sk, int level, int optname, 
				  char __user *optval, unsigned int optlen);
	int	    (*getsockopt)(struct sock *sk, int level, int optname, 
				  char __user *optval, int __user *optlen);

...
}

(2)tcp_hader_len:傳送資料段TCP協議頭的長度。

(3)xmit_size_goal:傳輸資料段你的目標。

(4)pred_flags:TCP協議預定向完成標誌。

(5)rcv_nxt:下一個輸入資料段序列號。

(6)snd_nxt:下一個傳送資料段的序列號。

(7)prequeue:輸入佇列。

(8)task:使用者程序,接受prequeue佇列中資料段的使用者程序。

(9)iov:向量指標,指向使用者地址空間中存放資料的陣列。

(10)memory:在prequeue佇列中所有socket buffer中資料長度的總和。

(11)Len:prequeue佇列上socket buffer緩衝區的個數。

(12)DMA,當網路裝置支援Scatter/Gather I/O功能,可以利用DMA直接訪問記憶體,將資料非同步從網路裝置硬體緩衝區複製到應用程式地址空間的緩衝區。

...

//DMA
#ifdef CONFIG_NET_DMA
		/* members for async copy */
		struct dma_chan		*dma_chan;
		int			wakeup;
		struct dma_pinned_list	*pinned_list;
		dma_cookie_t		dma_cookie;
#endif

...

struct tcp_sock:

struct tcp_sock {
	/* inet_connection_sock has to be the first member of tcp_sock */
	struct inet_connection_sock	inet_conn;				//面向連線結構體
	u16	tcp_header_len;	/* Bytes of tcp header to send		TCP協議頭長度*/
	u16	xmit_size_goal_segs; /* Goal for segmenting output packets 輸出資料段目標*/

/*
 *	Header prediction flags
 *	0x5?10 << 16 + snd_wnd in net byte order
 */
	__be32	pred_flags;


 	u32	rcv_nxt;	/* What we want to receive next 	*/
	u32	copied_seq;	/* Head of yet unread data		*/
	u32	rcv_wup;	/* rcv_nxt on last window update sent	*/
 	u32	snd_nxt;	/* Next sequence we send		*/

 	u32	snd_una;	/* First byte we want an ack for	*/
 	u32	snd_sml;	/* Last byte of the most recently transmitted small packet */
	u32	rcv_tstamp;	/* timestamp of last received ACK (for keepalives) */
	u32	lsndtime;	/* timestamp of last sent data packet (for restart window) */

...

}

4、TCP協議選項Options

TCP協議是可配置協議,TCP選項可以通過setsockopt系統呼叫來設定,也可以通過getsockopt來返回當前TCP選項,TCP選項在struct tcp_sock資料結構中定義了,下面介紹TCP選項值的含義:

(1)、TCP_CORK/nonagle

這個選項對應struct tcp_sock中的nonagle資料域,如果配置了這個選項接受應用層的資料後TCP不會立即傳送資料段,知道資料段達到TCP協議資料段最大值,它使應用程式可以在路由的MTU小於TCP的資料段最大段大小(MSS)時停止傳送。TCP_CORK和TCP_NODELAY選項是互斥的。

(2)、TCP_DEFER_ACCEPT/defer_accept

應用層序呼叫者在資料還沒到達套接字之前,可以處於休眠狀態。但當資料到達套接字時應用程式被喚醒,如果等待超時應用層序也會被喚醒,盜用者設定一個時間值來描述應用程式等待資料到達的時間。該選項儲存struct sock 資料結構的defer_accept資料域。

(3)、TCP_INFO

使用此選項可以獲取大部分套接字的配置資訊,獲取的配置資訊儲存在struct tcp_info資料結構中:

struct tcp_info {
	__u8	tcpi_state;		//當前tcp連線狀態
	__u8	tcpi_ca_state;
	__u8	tcpi_retransmits;
	__u8	tcpi_probes;
	__u8	tcpi_backoff;
	__u8	tcpi_options;
	__u8	tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;

	__u32	tcpi_rto;
	__u32	tcpi_ato;
	__u32	tcpi_snd_mss;
	__u32	tcpi_rcv_mss;

	__u32	tcpi_unacked;
	__u32	tcpi_sacked;
	__u32	tcpi_lost;
	__u32	tcpi_retrans;
	__u32	tcpi_fackets;

	/* Times. */
	__u32	tcpi_last_data_sent;		//最近傳送資料的時間戳
	__u32	tcpi_last_ack_sent;     /* Not remembered, sorry. */
	__u32	tcpi_last_data_recv;		//最近接受資料的時間戳
	__u32	tcpi_last_ack_recv;

	/* Metrics. */
	__u32	tcpi_pmtu;				//mtu
	__u32	tcpi_rcv_ssthresh;
	__u32	tcpi_rtt;
	__u32	tcpi_rttvar;
	__u32	tcpi_snd_ssthresh;
	__u32	tcpi_snd_cwnd;
	__u32	tcpi_advmss;
	__u32	tcpi_reordering;

	__u32	tcpi_rcv_rtt;
	__u32	tcpi_rcv_space;

	__u32	tcpi_total_retrans;
};

(4)、TCP_KEEPCNT

此選項可以設定TCP在斷開連線之前可以通過套接字傳送多少個保持連線活動(keepalive)的探測資料段,該選項存放在struct tcp_sock資料結構中的keepalive_probes資料域,如果設定了該選項還要設定套接字層的SO_KEEPALIVE選項。

(5)、TCP_KEEPIDEL

TCP開始傳送連線是否保持活動的探測資料段之前,連線處於空閒狀態的時間值,保持在struct tcp_sock資料結構中的keepalive_time資料域,預設值是2個小時,如果設定此選項還要設定套接字層的SO_KEEPALIVE選項。

(6)、TCP_KEEPINTVL 

設定兩次傳送探測連線保持活動資料段之前要等待多少秒,該值存放在struct tcp_sock資料結構的deepalive_intvl資料域中,初始值是75秒。

(7)TCP_LINGER2

此選項指處於FIN_WAIT2狀態的孤立套接字還應保持存活多長時間,如果是0則關閉選項,linux使用常規方式處理FIN_WAIT_2和TIME_WAIT狀態,如果值小於0,則套接字立即從FIN_WAIT2狀態進入CLOSED狀態,不經過FIN_WAIT,此選項儲存在struct tcp_sock資料結構的liinger2資料域中,預設值由sysctl決定。

(8)、TCP_NODELAY

如果設定了該選項則TCP會立即把資料傳送到網路層,而不會等待資料達到pmtu才傳送,改值儲存在struct tcp_sock資料結構的nonagle資料域中,和TCP_CORK互斥。

(9)、TCP_MAXSEG

此選項指定TCP最大資料段的大小值,TCP的MSS值也是此選項決定,但MSS的值不能超過MTU,TCP連線兩端可以協商資料段大小。

(10)、TCP_QUICKACK

當設定這個值為1,就會關閉延遲迴答,延遲迴答是linux TCP的一個常規模式。延遲迴答時ACK資料會延遲到可以與一個等待發送到另一端的資料段合併時,才會傳送出去,如果設定為1,sruct tcp_sock資料結構中的ack部分的pingpong資料域設為0,就可以禁止延遲傳送。

(11)、TCP_SYNCNT

這個選項是TCP在嘗試建立連線,如果連線沒有建立起來,要重傳多少此SYN包後就放棄建立連線請求,該選項儲存在struct tcp_syn_retries資料域中。

(12)、TCP_WINDOW_CLAMP

指定套接字視窗大小,視窗的最小值是SOCK_MIN_RCVBUF除以2,等於128個位元組,該選項儲存在struct tcp_sock資料結構中的windown_clamp資料域。

4、struct msghdr

應用層傳給套接字的資訊結構體是struct msghdr,在傳送處理函式中把資料從應用層複製到核心地址空間,結構體定義在include/linux/socket.c檔案中

struct msghdr {
	void	*	msg_name;	/* Socket name 套接字名字,也是對端ip地址*/
	int		msg_namelen;	/* Length of name		目的地址長度*/
	struct iovec *	msg_iov;	/* Data blocks		應用層緩衝區起始地址	*/
	__kernel_size_t	msg_iovlen;	/* Number of blocks	緩衝區陣列個數	*/
	void 	*	msg_control;	/* Per protocol magic (eg BSD file descriptor passing) 控制資訊*/
	__kernel_size_t	msg_controllen;	/* Length of cmsg list 控制資訊長度*/
	unsigned	msg_flags;	//接受資料的標誌
};

msg_name:套接字名字,不是實際套接字名,他是一個指標,指向struct sockaddr_in資料結構的變數,struct sockaddr_in資料結構包含了傳送資料段的目標IP地址和埠號。

msg_namelen:msg_name指向的地址資訊長度。

msg_iov:指向應用層緩衝區陣列的起始地址。

msg_iovlen:msg_iov緩衝區陣列中緩衝區的個數。

msg_control:儲存向套接字層下發的協議控制資訊。

msg_flags:套接字從應用層接受到的控制標誌。

msg_falgs中標誌位有效值
標誌 說明
MSG_OOB 1 請求out-of-bound資料
MSG_PEEK 2 從接受佇列中返回資料,但不把資料從佇列中移走
MSG_DONTROUTE 4 不對該資料路由,常用於ping歷程中傳送ICMP資料包
MSG_TRYHARD 4 不用於TCP/IP協議棧,與MSG_DONTROUTE同義
MSG_CTRUNC 8 用於SOL_IP內部控制資訊
MSG_PROBE 0x10 用於發現MTU資料段
MSG_TRUNC 0x20 truncate訊息
MSG_NONTWAIT 0x40 呼叫應用程式是否用於不被阻塞的I/O
MSG_EOR 0x80 資訊結束
MSG_WAITALL 0x100 在返回資料前等待所有資料到達
MSG_FIN 0x200 TCP結束資料段(FIN)
MSG_SYN 0x800 TCP同步資料段(SYN)
MSG_CONFIRM 0x800 傳送資料包前確認路徑有效
MSG_RST 0x1000 TCP復位連線資料段(RST)
MSG_ERRQUEUE 0x2000 從錯誤佇列讀取資料段
MSG_NOSIGNAL 0x4000 當確認連線斷開,不產生SIGPIPE訊號
MSG_MORE 0x8000 指明將傳送更多的資料資訊