TCP在进行建立tcp三次握手连接时,客户端在发送SYN包时,会带有一些选项的信息,服务器在接收到SYN包时,会调用tcp_parse_options函数进行解析,下面是一些选项的概略图:
c/* Look for tcp options. Normally only called on SYN and SYNACK packets.
* But, this can also be called on packets in the established flow when
* the fast version below fails.
*/
void tcp_parse_options(const struct net *net,
const struct sk_buff *skb,
struct tcp_options_received *opt_rx, int estab,
struct tcp_fastopen_cookie *foc)
{
const unsigned char *ptr;
const struct tcphdr *th = tcp_hdr(skb); //取得tcp的头部
int length = (th->doff * 4) - sizeof(struct tcphdr); //取得除tcp头部的的长度
ptr = (const unsigned char *)(th + 1); //跳过tcp的头部
opt_rx->saw_tstamp = 0; //Saw TIMESTAMP on last packet
while (length > 0) {
int opcode = *ptr++; //选项kind
int opsize; //选项长度,包括kind和length
switch (opcode) {
case TCPOPT_EOL: //End of options ,options的结束标志
return;
case TCPOPT_NOP: /* Ref: RFC 793 section 3.1 */ //填充标志,为了字节对齐
length--;
continue;
default:
opsize = *ptr++;
if (opsize < 2) /* "silly options" */ //选项大小小于2个字节,直接退出
return;
if (opsize > length)
return; /* don't parse partial options */
switch (opcode) {
case TCPOPT_MSS://对端发送的长度的限制,整个tcp传输segment的过程中不能大于这个值
if (opsize == TCPOLEN_MSS && th->syn && !estab) {
u16 in_mss = get_unaligned_be16(ptr);
if (in_mss) {
if (opt_rx->user_mss && //mss requested by user ioctl 用户调用ioctl函数设置的,
opt_rx->user_mss < in_mss)
in_mss = opt_rx->user_mss;
opt_rx->mss_clamp = in_mss; //Maximal mss,negotiated at connection setup
}
}
break;
case TCPOPT_WINDOW: //窗口扩大选项
if (opsize == TCPOLEN_WINDOW && th->syn &&
!estab && net->ipv4.sysctl_tcp_window_scaling) { //sysctl_tcp_window_scaling,这是linux配置文件中的一个选项
__u8 snd_wscale = *(__u8 *)ptr;
opt_rx->wscale_ok = 1; //Wscale seen on SYN packet
if (snd_wscale > TCP_MAX_WSCALE) { //TCP_MAX_WSCALE:14u,Maximal number of window scale according to RFC1323
net_info_ratelimited("%s: Illegal window scaling value %d > %u received\n",
__func__,
snd_wscale,
TCP_MAX_WSCALE);
snd_wscale = TCP_MAX_WSCALE;
}
opt_rx->snd_wscale = snd_wscale;
}
break;
case TCPOPT_TIMESTAMP: //时间戳,TIMESTAMP seen on SYN packet
if ((opsize == TCPOLEN_TIMESTAMP) &&
((estab && opt_rx->tstamp_ok) ||
(!estab && net->ipv4.sysctl_tcp_timestamps))) { //sysctl_tcp_timestamps,这是linux配置文件中的一个选项
opt_rx->saw_tstamp = 1;
opt_rx->rcv_tsval = get_unaligned_be32(ptr); //rcv_tsval :指明了发送端在发送TCP segment时的timestamp,接收端在ACK这个segment时,会将rcv_tsval 值回显在rcv_tsecr 中,由于tcp连接是全双工的,接收端也会把自己timestamp存放在rcv_tsval 中,(接收端和发送端都是相对的)
opt_rx->rcv_tsecr = get_unaligned_be32(ptr + 4);
}
break;
case TCPOPT_SACK_PERM: //发送端和接收端都支持这个选项才行,
if (opsize == TCPOLEN_SACK_PERM && th->syn &&
!estab && net->ipv4.sysctl_tcp_sack) {
opt_rx->sack_ok = TCP_SACK_SEEN;
tcp_sack_reset(opt_rx);
}
break;
case TCPOPT_SACK://TCPOPT_SACK这个选项引入是为了解决发送端会重复发送已经被接收端确认过的包,影响传输效率,这个选项结合ACK选项,可以告诉发送端丢失了哪些包,从而发送端只需传输丢失的包,大大的提升了传输效率,
由于options的格式一般是kind,length,options,TCPOPT_SACK最多只有四组,60字节-TCP头的固定20字节=40字节,
if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) &&
!((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) &&
opt_rx->sack_ok) {
TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *)th;
}
break;
#ifdef CONFIG_TCP_MD5SIG //这是为了安全认证用的,TLS/SSL用的比较多,一般是生成摘要,一段hash值
case TCPOPT_MD5SIG:
/*
* The MD5 Hash has already been
* checked (see tcp_v{4,6}_do_rcv()).
*/
break;
#endif
case TCPOPT_FASTOPEN: //这是快速重传的选项,
tcp_parse_fastopen_option(
opsize - TCPOLEN_FASTOPEN_BASE,
ptr, th->syn, foc, false);
break;
case TCPOPT_EXP: //保留用作实验用途
/* Fast Open option shares code 254 using a
* 16 bits magic number.
*/
if (opsize >= TCPOLEN_EXP_FASTOPEN_BASE &&
get_unaligned_be16(ptr) ==
TCPOPT_FASTOPEN_MAGIC) //一个魔幻数字,Magic number to be after the option value for sharing tcp
tcp_parse_fastopen_option(opsize
TCPOLEN_EXP_FASTOPEN_BASE,
ptr + 2, th->syn, foc, true);
break;
}
ptr += opsize-2;
length -= opsize; //减去一个option的大小,接着再进入while循环,进行下一个选项的解析。
}
}
}