客户端发送的SYN请求到达服务器的网卡后,进入服务器操作系统的网络协议栈,经过链路层和网络层的处理后,抵达TCP协议的入口函数。TCPv4的入口函数是tcp_v4_rcv,TCPv6的入口函数是tcp_v6_rcv。下面对tcp_v4_rcv进行分析:
1961 int tcp_v4_rcv(struct sk_buff *skb)
1962 {
1963 const struct iphdr *iph;
1964 const struct tcphdr *th;
1965 struct sock *sk;
1966 int ret;
...
1975 if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
1976 goto discard_it;
1977
1978 th = tcp_hdr(skb);
1979
1980 if (th->doff < sizeof(struct tcphdr) / 4) //检查TCP头长度是否大于最小值
1981 goto bad_packet;
1982 if (!pskb_may_pull(skb, th->doff * 4))
1983 goto discard_it;
1984
1985 /* An explanation is required here, I think.
1986 * Packet length and doff are validated by header prediction,
1987 * provided case of th->doff==0 is eliminated.
1988 * So, we defer the checks. */
1989 if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb)) //校验检验和
1990 goto csum_error;
1991
1992 th = tcp_hdr(skb);
1993 iph = ip_hdr(skb);
1994 TCP_SKB_CB(skb)->seq = ntohl(th->seq);
1995 TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
1996 skb->len - th->doff * 4);
1997 TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
1998 TCP_SKB_CB(skb)->when = 0;
1999 TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
2000 TCP_SKB_CB(skb)->sacked = 0;
2001
2002 sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
2003 if (!sk)
2004 goto no_tcp_socket;
...
2023
2024 bh_lock_sock_nested(sk); //加自旋锁,以防止其它软中断同时访问当前socket
2025 ret = 0;
2026 if (!sock_owned_by_user(sk)) {//如果没有进程锁定此socket;对于SYN此条件为真
...
2035 {
2036 if (!tcp_prequeue(sk, skb)) //判断是否加入到prequeue中;若无进程等待使用prequeue的话则调用tcp_v4_do_rcv函数进行处理
2037 ret = tcp_v4_do_rcv(sk, skb);//进入主处理函数
2038 }
...
2043 goto discard_and_relse;
2044 }
2045 bh_unlock_sock(sk);
2046
2047 sock_put(sk);
2048
2049 return ret;
2050
代码解析:
1975:pskb_may_pull的作用就是检测skb对应的主buf中是否有足够的空间来pull出len长度,如果不够就重新分配skb并将frags中的数据整合进连续空间中;SYN包则不存在frags中的数据。
1994-2000:将报文中的一些信息记录到skb的cb字段中以便使用;
2002:__inet_lookup_skb会根据报文的源/目的IP和源/目的端口等信息在tcp_hashinfo中查找连接。先查找ehash,即established连接表,对于SYN请求这次查找会失败;然后再找listening_hash,这时会找到server端在listen系统调用中加入到listening_hash表中的socket。
下面分析主处理函数tcp_v4_do_rcv:
1800 int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
1801 {
1802 struct sock *rsk;
...
1835 if (sk->sk_state == TCP_LISTEN) {
1836 struct sock *nsk = tcp_v4_hnd_req(sk, skb); //查找request sock
1837 if (!nsk)
1838 goto discard;
1839
1840 if (nsk != sk) {
1841 sock_rps_save_rxhash(nsk, skb);
1842 if (tcp_child_process(sk, nsk, skb)) {
1843 rsk = nsk;
1844 goto reset;
1845 }
1846 return 0;
1847 }
1848 } else
1849 sock_rps_save_rxhash(sk, skb);
1850
1851 if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
1852 rsk = sk;
1853 goto reset;
1854 }
1855 return 0;
...
server端socket的状态一定是TCP_LISTEN,即1835行的判断成立。tcp_v4_hnd_req用于查找一个处于“半建立”状态(即发送SYN|ACK,等待ACK)的连接:
1739 static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_bu