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_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 | 指明将发送更多的数据信息 |