tcp syn-synack-ack 服务端发送syn-ack

tcp_v4_send_synack()用于发送SYNACK段,在tcp_v4_conn_request()中被调用。

首先调用tcp_make_synack()构造SYNACK段,主要是构造TCP报头和初始化skb中的一些字段

/*
 * 该结构主要描述双方的地址、所支持的TCP选项等
  tcp_request_sock包含inet_request_sock,inet_request_sock包含request_sock
 */
struct inet_request_sock {
    struct request_sock    req;
    /*
     * 本地端口号  本地IP地址 对端IP地址
     */
#define ir_loc_addr        req.__req_common.skc_rcv_saddr
#define ir_rmt_addr        req.__req_common.skc_daddr
#define ir_num            req.__req_common.skc_num
#define ir_rmt_port        req.__req_common.skc_dport
#define ir_v6_rmt_addr        req.__req_common.skc_v6_daddr
#define ir_v6_loc_addr        req.__req_common.skc_v6_rcv_saddr
#define ir_iif            req.__req_common.skc_bound_dev_if
#define ir_cookie        req.__req_common.skc_cookie
#define ireq_net        req.__req_common.skc_net
#define ireq_state        req.__req_common.skc_state
#define ireq_family        req.__req_common.skc_family
/*
     * 发送窗口扩大因子,即要把TCP首部中指定的滑动窗口大小
     * 左移snd_wscale位后,作为真正的滑动窗口大小。在TCP
     * 首部中,滑动窗口大小值为16位的,而snd_wscale的值最大
     * 只能为14。所以,滑动窗口最大可被扩展到30位,在协议栈
     * 的实际实现中,可以看到窗口大小被置为5840,扩大因子为2,
     * 即实际的窗口大小为5840<<2=23360B
     */
    kmemcheck_bitfield_begin(flags);
    u16            snd_wscale : 4,
        /*
                 * 接收窗口扩大因子
                 */
                rcv_wscale : 4,
                /*
                 * 标识TCP段是否存在TCP时间戳选项
                 */
                tstamp_ok  : 1,
                /*
                 * 标识是否支持SACK,支持则该选项能出现在SYN段中
                 */
                sack_ok       : 1,
                /*
                 * 标识是否支持窗口扩大因子,如果支持该选项也只能出现
                 * 在SYN段中
                 */
                wscale_ok  : 1,
                /*
                 * 标志是否启用了显式拥塞通知
                 */
                ecn_ok       : 1,
                /*
                 * 标识已接收到第三次握手的ACK段,但是由于服务器繁忙
                 * 或其他原因导致未能建立起连接,此时可根据该标志重新
                 * 给客户端发送SYN+ACK段,再次进行连接的建立。该标志
                 * 的设置同时受sysctl_tcp_abort_on_overflow的控制
                 */
                acked       : 1,
                no_srccheck: 1;
    kmemcheck_bitfield_end(flags);
    u32                     ir_mark;
    //服务器端在接收到SYN后,会解析SKB中的ip选项字段,见tcp_v4_save_option
    union {
        struct ip_options_rcu    *opt;
        struct sk_buff        *pktopts;
    };
};

 

 

static const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops = {
    .mss_clamp    =    TCP_MSS_DEFAULT,
#ifdef CONFIG_TCP_MD5SIG
    .req_md5_lookup    =    tcp_v4_md5_lookup,
    .calc_md5_hash    =    tcp_v4_md5_hash_skb,
#endif
    .init_req    =    tcp_v4_init_req,
#ifdef CONFIG_SYN_COOKIES
    .cookie_init_seq =    cookie_v4_init_sequence,
#endif
    .route_req    =    tcp_v4_route_req,
    .init_seq    =    tcp_v4_init_sequence,
    .send_synack    =    tcp_v4_send_synack,
};


/*
* 服务端用来处理客户端连接请求的函数 */ //服务器端收到SYN后,创建连接控制块request_sock。也就是收到第一步SYN的时候只是建立的连接控制块request_sock,当收到第三次ack的时候,才创建新的struct sock int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb) { /* Never answer to SYNs send to broadcast or multicast */ if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) goto drop; return tcp_conn_request(&tcp_request_sock_ops, &tcp_request_sock_ipv4_ops, sk, skb); drop: tcp_listendrop(sk); return 0; }
//服务器端收到SYN后,创建连接控制块request_sock
/。也就是收到第一步SYN的时候只是建立的连接控制块request_sock
,当收到第三次ack的时候,才创建新的struct sock
*/
int tcp_conn_request(struct request_sock_ops *rsk_ops,
             const struct tcp_request_sock_ops *af_ops,
             struct sock *sk, struct sk_buff *skb)
{
    struct tcp_fastopen_cookie foc = { .len = -1 };
    __u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn;
    struct tcp_options_received tmp_opt;
    struct tcp_sock *tp = tcp_sk(sk);
    struct net *net = sock_net(sk);
    struct sock *fastopen_sk = NULL;
    struct dst_entry *dst = NULL;
    struct request_sock *req;
    bool want_cookie = false;
/*如果启用了cookie机制,则会在第三步收到ACK的时候在tcp_v4_hnd_req中 
的cookie_v4_check对之前发送的ack+syn进行检查,检查过程见cookie_v4_check
    */struct flowi fl;

    /* TW buckets are converted to open requests without
     * limitations, they conserve resources and peer is
     * evidently real one.
     */
      */
    /*
     * 如果SYN请求连接队列已满并且isn为零,则需做特别处理。
     * 这里的isn就是TCP_SKB_CB(skb)->when,而TCP_SKB_CB(skb)->when
     * 在TCP接收处理一开始就被清零,因此这里isn为零总是成立
     */
    if ((net->ipv4.sysctl_tcp_syncookies == 2 ||//sysctl_tcp_syncookies=2无条件生成syncookie
         inet_csk_reqsk_queue_is_full(sk)) && !isn) {//或者请求队列太长, 并且当前不是timewait
        want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);//sysctl_tcp_syncookies>0, 并未当前socket打印一次告警
        if (!want_cookie)//队列满了,但不使用syncookie,则丢弃
            goto drop;
    }


    /* Accept backlog is full. If we have already queued enough
     * of warm entries in syn queue, drop request. It is better than
     * clogging syn queue with openreqs with exponentially increasing
     * timeout.
     */
     /*
     * 如果连接队列长度已达到上限且SYN请求队列中至少有一个握手过程中
     * 没有重传过的段,则丢弃当前连接请求.
     *  如果半连接队列中未重传的请求块数量大于1,
     * 则表示未来可能有2个完成的连接,这些新完成
     * 的连接要放到连接队列中,但此时连接队列已满
     * 。如果在接收到三次握手中最后的ACK后连接队列
     * 中没有空闲的位置,会忽略接收到的ACK包,连接
     * 建立会推迟,所以此时最好丢掉部分新的连接请
     * 求,空出资源以完成正在进行的连接建立过程。
     * 还要注意,这个判断并没有考虑半连接队列是否
     * 已满的问题。从这里可以看出,即使开启了
     * SYN cookies机制并不意味着一定可以完成连接的建立。
     * static inline int inet_csk_reqsk_queue_young(const struct sock *sk)
{
    return reqsk_queue_len_young(&inet_csk(sk)->icsk_accept_queue);
}static inline int reqsk_queue_len_young(const struct request_sock_queue *queue)
{
    return atomic_read(&queue->young);
}
static inline bool sk_acceptq_is_full(const struct sock *sk)
{
    return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}
*/ if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {//accept队列满,但是syn队列依然有可能被accept的连接,此时丢弃 NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); goto drop; } /* * 可以接收并处理连接请求,调用inet_reqsk_alloc()分配一个连接请求 * 块,用于保存连接请求信息,同时初始化在建立连接过程中用来发送 * ACK、RST段的操作集合,以便在建立连接过程中能方便地调用这些接口 */ //rsk_ops ===tcp_request_sock_ops req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);//分配request_sock, 进入TCP_NEW_SYN_RECV状态 if (!req) goto drop; //af_ops====tcp_request_sock_ipv4_opss tcp_rsk(req)->af_specific = af_ops;//tcp_request_sock_ipv4_ops /* * 清除TCP选项后初始化mss_clamp和user_mss。 */ tcp_clear_options(&tmp_opt); tmp_opt.mss_clamp = af_ops->mss_clamp;//TCP_MSS_DEFAULT=536 tmp_opt.user_mss = tp->rx_opt.user_mss;//listen sock设置的或是tw的 /* * 解析SYN段中的TCP选项 */ tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);//开启syncookie后则不用考虑fastopen, syncookie不允许使用tcp扩展 if (want_cookie && !tmp_opt.saw_tstamp) //开启syncookie,但是不带timestamp tcp_clear_options(&tmp_opt);//清除wscale,sack_ok等选项,因为没地方存 /* * 初始化该连接中是否启用时间戳的选项tstamp_ok */ tmp_opt.tstamp_ok = tmp_opt.saw_tstamp; /* * 根据接收到SYN段中的选项和序号来初始化连接请求块信息 */ tcp_openreq_init(req, &tmp_opt, skb, sk); /* Note: tcp_v6_init_req() might override ir_iif for link locals */ inet_rsk(req)->ir_iif = inet_request_bound_dev_if(sk, skb); /* * 初始化TCP层次的连接请求信息块,包括目的地址、源地址, * 并调用tcp_v4_save_options从IP层私有控制块中获取IP * 选项保存到传输控制块的opt中,包括MSS、窗口扩大 * 因子、显式拥塞通知等 */ af_ops->init_req(req, sk, skb);//tcp_v4_init_req 会调用tcp_v4_save_options if (security_inet_conn_request(sk, skb, req)) goto drop_and_free; if (!want_cookie && !isn) {//不需要生成syncookie,也不是从timewait recycle的新的sock /* VJ's idea. We save last timestamp seen * from the destination in peer table, when entering * state TIME-WAIT, and check against it before * accepting new connection request. * * If "isn" is not zero, this request hit alive * timewait bucket, so that all the necessary checks * are made in the function processing timewait state. */ /* * 进入TIMEWAIT状态时,从对端信息块中获取时间戳,在新的 * 连接请求之前检测PAWS */ if (tcp_death_row.sysctl_tw_recycle) { bool strict; dst = af_ops->route_req(sk, &fl, req, &strict); //tcp_v4_route_req //当起了快速回收tw_recycle的时候,这里可能有问题,可能连接建立不上,针对TCP时间戳PAWS漏洞的代码。 见:http://blog.chinaunix.net/uid-736168-id-376061.html //针对TCP时间戳PAWS漏洞,造成服务器端收到SYN的时候不回收SYN+ACK,解决办法是对方不要发送时间戳选项,同时关闭tcp_timestamps见tcp_v4_conn_request if (dst && strict && !tcp_peer_is_proven(req, dst, true, tmp_opt.saw_tstamp)) { NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED); goto drop_and_release; /* 1 tcp的option有 time stamp字段. 2 tcp_tw_recycle有设置。 3 在路由表中是否存在完全相同的流(如果打开了xfrm的话, 还要比较端口,默认xfrm应该是打开的),如果存在则直接返回. 4 并且数据包的源地址和新请求的源地址相同. 5 根据路由表以及源地址能够查找到保存的peer (这个可以看我以前的blog,也就是保存了一些连接统计信息). 6 当前时间(接收到syn)比最后一次的时间(time stamp)小于60秒. 7 已经存在peer的最近一次时间戳要大于当前请求进来的时间戳. 从上面可以看到,上面的条件中1/2都是 server端可以控制的,而其他的条件, 都是很容易就满足的,因此我们举个例子。 如果客户端是NAT出来的,并且我们server端有打开tcp_tw_recycle , 并且time stamp也没有关闭,那么假设第一个连接进来,然后关闭,此时这个句柄处于time wait状态,然后很快(小于60秒)又一个客户端(相同的源地址,如果打开了xfrm还要相同的端口号)发一个syn包,此时linux内核就会认为这个数据包异常的,因此就会丢掉这个包,并发送rst。 而现在大部分的客户端都是NAT出来的,因此建议tw_recycle还 是关闭,或者说server段关闭掉time stamp(/proc/sys/net/ipv4/tcp_timestamps). */ } } /* Kill the following clause, if you dislike this way. */ //如果没开启sysctl_tw_recycle和syncookie,最后1/4的syn请求需要验证过去的连接信? /* * 未启动syncookies的情况下受到synflood攻击,则丢弃接收到的段 */ else if (!net->ipv4.sysctl_tcp_syncookies && (sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) < (sysctl_max_syn_backlog >> 2)) && !tcp_peer_is_proven(req, dst, false, tmp_opt.saw_tstamp)) {//如果不存在tcp metric或者过去的连接信息则丢弃 /* Without syncookies last quarter of * backlog is filled with destinations, * proven to be alive. * It means that we continue to communicate * to destinations, already remembered * to the moment of synflood. */ pr_drop_req(req, ntohs(tcp_hdr(skb)->source), rsk_ops->family); goto drop_and_release; } isn = af_ops->init_seq(skb);//tcp_v4_init_sequence,根据四元组,随机数,当前高精度时间来生成isn } if (!dst) { dst = af_ops->route_req(sk, &fl, req, NULL);//tcp_v4_route_req if (!dst) goto drop_and_free; } tcp_ecn_create_request(req, skb, sk, dst); if (want_cookie) { /* * 如果启动了syncookies,则每60秒警告一次可能受 * synflood攻击,同时由客户端IP地址、客户端端口、 * 服务器IP地址、服务器端口、客户端初始序列号 * 等要素经hash运算后加密得到服务端初始化序列号 */ //如果开启了syncookie选项,则需要检查收到的第三步ack和这个isn值是否一致 isn = cookie_init_sequence(af_ops, sk, skb, &req->mss); //cookie_v4_init_sequence生成syncookie,并作为ack的起始序号 req->cookie_ts = tmp_opt.tstamp_ok; if (!tmp_opt.tstamp_ok) inet_rsk(req)->ecn_ok = 0; } tcp_rsk(req)->snt_isn = isn; tcp_rsk(req)->txhash = net_tx_rndhash(); tcp_openreq_init_rwin(req, sk, dst);//设置初始化rwnd if (!want_cookie) { tcp_reqsk_record_syn(sk, req, skb);//如果设置保存TCP_SAVE_SYN标记,则保存 fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst); //验证后创建fastopen sock,并把数据部分放入接收队列中 } if (fastopen_sk) {//验证并创建fastsocket成功, 进入TCP_SYN_RCV状态 af_ops->send_synack(fastopen_sk, dst, &fl, req, &foc, TCP_SYNACK_FASTOPEN);//tcp_v4_send_synac /* Add the child socket directly into the accept queue */ inet_csk_reqsk_queue_add(sk, req, fastopen_sk);//添加到等待accept的队列 sk->sk_data_ready(sk); bh_unlock_sock(fastopen_sk); sock_put(fastopen_sk); } else { tcp_rsk(req)->tfo_listener = false; /* * 将连接请求块保存到其父传输控制块中的散列表中 */ if (!want_cookie) inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);//插入ehash,并设置定时器 /* * 调用__tcp_v4_send_synack()组织并发送SYN+ACK段给客户端;如果 * 启用了syncookies,则是根据序号来判断三次握手的,因此无需保存 * 连接请求,直接将其释放 */ //如果是cookie,则真正的tcp_request_sock在第三步ack的时候在cookie_v4_check中创建 af_ops->send_synack(sk, dst, &fl, req, &foc, !want_cookie ? TCP_SYNACK_NORMAL : TCP_SYNACK_COOKIE);//tcp_v4_send_synack if (want_cookie) { reqsk_free(req);//启用syncookie的话,可以直接释放req return 0; } } reqsk_put(req); return 0; drop_and_release: dst_release(dst); drop_and_free: reqsk_free(req); drop: tcp_listendrop(sk); return 0;

 

/*
 *    Send a SYN-ACK after having received a SYN.
 *    This still operates on a request_sock only, not on a big
 *    socket.
 */
 /*
synack发送
tcp_make_synack主要是根据需要设置synack的tcp选项, 使用syncookie的时候服务端不保存状态,会把tcp扩展项编码到timestamp中,把syncookie作为seq回传;
对于fastopen请求,则会设置好fastopen cookie tcp选项回传,并对接收到的数据部分进行ack
因为synack不用分片并且必须有路由缓存,直接调用ip_build_and_send_pkt()来发送
 */
static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst,
                  struct flowi *fl,
                  struct request_sock *req,
                  struct tcp_fastopen_cookie *foc,
                  enum tcp_synack_type synack_type)
{
    const struct inet_request_sock *ireq = inet_rsk(req);
    struct flowi4 fl4;
    int err = -1;
    struct sk_buff *skb;

    /* First, grab a route. */ /* 路由为空则查路由 */
    if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL)
        return -1;
 /* 构造syn+ack包 */
    skb = tcp_make_synack(sk, dst, req, foc, synack_type);

    if (skb) {/* 生成校验码 */
        __tcp_v4_send_check(skb, ireq->ir_loc_addr, ireq->ir_rmt_addr);

        err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr,
                        ireq->ir_rmt_addr,
                        ireq->opt);
        err = net_xmit_eval(err);
    }

    return err;
}

 

/**
 * tcp_make_synack - Prepare a SYN-ACK.
 * sk: listener socket
 * dst: dst entry attached to the SYNACK
 * req: request_sock pointer
 *
 * Allocate one skb and build a SYNACK packet.
 * @dst is consumed : Caller should not use it again.
 */
struct sk_buff *tcp_make_synack(const struct sock *sk, struct dst_entry *dst,
                struct request_sock *req,
                struct tcp_fastopen_cookie *foc,
                enum tcp_synack_type synack_type)
{
    struct inet_request_sock *ireq = inet_rsk(req);
    const struct tcp_sock *tp = tcp_sk(sk);
    struct tcp_md5sig_key *md5 = NULL;
    struct tcp_out_options opts;
    struct sk_buff *skb;
    int tcp_header_size;
    struct tcphdr *th;
    u16 user_mss;
    int mss;
/* 分配skb用于发送SYNACK **/
    skb = alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC);
    if (unlikely(!skb)) {
        dst_release(dst);
        return NULL;
    }
    /* Reserve space for headers. */ /* 
    拓展headroom,为MAC、IP、TCP协议头预留空间
    保留头部空间 */
    skb_reserve(skb, MAX_TCP_HEADER);
    
    switch (synack_type) {
    case TCP_SYNACK_NORMAL:/* skb关联控制块 */
        skb_set_owner_w(skb, req_to_sk(req));
        break;
    case TCP_SYNACK_COOKIE:
        /* Under synflood, we do not attach skb to a socket,
         * to avoid false sharing.
         */
        break;
    case TCP_SYNACK_FASTOPEN:
        /* sk is a const pointer, because we want to express multiple
         * cpu might call us concurrently.
         * sk->sk_wmem_alloc in an atomic, we can promote to rw.
         */
        skb_set_owner_w(skb, (struct sock *)sk);
        break;
    }
    /* 保存路由缓存的地址 */
    skb_dst_set(skb, dst); /* 设置路由缓存 */
/* 从路由缓存中获取本端的通告MSS */
    mss = dst_metric_advmss(dst); /* mss取从路由表中查询的mss与user_mss之间的较小值 */
    user_mss = READ_ONCE(tp->rx_opt.user_mss);
    if (user_mss && user_mss < mss)
        mss = user_mss;

    memset(&opts, 0, sizeof(opts));
#ifdef CONFIG_SYN_COOKIES
    if (unlikely(req->cookie_ts))
        skb->skb_mstamp.stamp_jiffies = cookie_init_timestamp(req);
    else
#endif
    skb_mstamp_get(&skb->skb_mstamp); /* 获取时间戳 */

#ifdef CONFIG_TCP_MD5SIG
    rcu_read_lock();
    md5 = tcp_rsk(req)->af_specific->req_md5_lookup(sk, req_to_sk(req));
#endif
    skb_set_hash(skb, tcp_rsk(req)->txhash, PKT_HASH_TYPE_L4);
    tcp_header_size = tcp_synack_options(req, mss, skb, &opts, md5, foc) +
              sizeof(*th);/* 设置tcp选项 */
/* 构造填充tcp头 */
    skb_push(skb, tcp_header_size);
    skb_reset_transport_header(skb);

    th = (struct tcphdr *)skb->data;
    memset(th, 0, sizeof(struct tcphdr));
    th->syn = 1;
    th->ack = 1;
    tcp_ecn_make_synack(req, th);
    th->source = htons(ireq->ir_num);/* 源端口 */
    th->dest = ireq->ir_rmt_port;/* 目的端口 */
    /* Setting of flags are superfluous here for callers (and ECE is
     * not even correctly set)
     *//* 初始化skb中的一些控制字段 */
    tcp_init_nondata_skb(skb, tcp_rsk(req)->snt_isn,
                 TCPHDR_SYN | TCPHDR_ACK);

    th->seq = htonl(TCP_SKB_CB(skb)->seq);
    /* XXX data is queued and acked as is. No buffer/window check */
    th->ack_seq = htonl(tcp_rsk(req)->rcv_nxt);

    /* RFC1323: The window in SYN & SYN/ACK segments is never scaled. */
    th->window = htons(min(req->rsk_rcv_wnd, 65535U)); /* 设置窗口 */
     /* 把TCP选项实例tcp_out_options写到skb中 */
    tcp_options_write((__be32 *)(th + 1), NULL, &opts);
    th->doff = (tcp_header_size >> 2); /* 设置首部长度 */
    __TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTSEGS);

#ifdef CONFIG_TCP_MD5SIG
    /* Okay, we have all we need - do the md5 hash if needed */
    if (md5)
        tcp_rsk(req)->af_specific->calc_md5_hash(opts.hash_location,
                           md5, req_to_sk(req), skb);
    rcu_read_unlock();
#endif

    /* Do not fool tcpdump (if any), clean our debris */
    skb->tstamp.tv64 = 0;
    return skb;

如果SYNACK段使用SYN Cookie,并且使用时间戳选项,则把TCP选项信息保存在SYNACK段中tsval的低6位

/*
 * when syncookies are in effect and tcp timestamps are enabled we encode
 * tcp options in the lower bits of the timestamp value that will be
 * sent in the syn-ack.
 * Since subsequent timestamps use the normal tcp_time_stamp value, we
 * must make sure that the resulting initial timestamp is <= tcp_time_stamp.
 */
 /* There is no TS_OPT_TIMESTAMP:
 * if ACK contains timestamp option, we already know it was
 * requested/supported by the syn/synack exchange.
 */
#define TSBITS    6
#define TSMASK    (((__u32)1 << TSBITS) - 1)

__u32 cookie_init_timestamp(struct request_sock *req)
{
    struct inet_request_sock *ireq;
    u32 ts, ts_now = tcp_time_stamp;
    u32 options = 0;

    ireq = inet_rsk(req);

    options = ireq->wscale_ok ? ireq->snd_wscale : TS_OPT_WSCALE_MASK;
    if (ireq->sack_ok)
        options |= TS_OPT_SACK;
    if (ireq->ecn_ok)
        options |= TS_OPT_ECN;

    ts = ts_now & ~TSMASK;
    ts |= options;
    if (ts > ts_now) {
        ts >>= TSBITS;
        ts--;
        ts <<= TSBITS;
        ts |= options;
    }
    return ts;
}

赋值TCP选项实例tcp_out_options,用于构造SYNACK段。

/* Set up TCP options for SYN-ACKs. */
static unsigned int tcp_synack_options(struct request_sock *req,
                       unsigned int mss, struct sk_buff *skb,
                       struct tcp_out_options *opts,
                       const struct tcp_md5sig_key *md5,
                       struct tcp_fastopen_cookie *foc)
{
    struct inet_request_sock *ireq = inet_rsk(req);
    unsigned int remaining = MAX_TCP_OPTION_SPACE;

#ifdef CONFIG_TCP_MD5SIG
    if (md5) {
        opts->options |= OPTION_MD5;
        remaining -= TCPOLEN_MD5SIG_ALIGNED;

        /* We can't fit any SACK blocks in a packet with MD5 + TS
         * options. There was discussion about disabling SACK
         * rather than TS in order to fit in better with old,
         * buggy kernels, but that was deemed to be unnecessary.
         */
        ireq->tstamp_ok &= !ireq->sack_ok;
    }
#endif

    /* We always send an MSS option. */
    opts->mss = mss;/* Max Segment Size选项 */
    remaining -= TCPOLEN_MSS_ALIGNED;

    if (likely(ireq->wscale_ok)) {/* Window Scaling选项 */
        opts->ws = ireq->rcv_wscale;
        opts->options |= OPTION_WSCALE;
        remaining -= TCPOLEN_WSCALE_ALIGNED;
    }
    if (likely(ireq->tstamp_ok)) {/* 时间戳选项 */
        opts->options |= OPTION_TS;
        opts->tsval = tcp_skb_timestamp(skb);
        opts->tsecr = req->ts_recent;
        remaining -= TCPOLEN_TSTAMP_ALIGNED;
    }
    if (likely(ireq->sack_ok)) {/* SACK Permit选项 */
        opts->options |= OPTION_SACK_ADVERTISE;
        if (unlikely(!ireq->tstamp_ok))
            remaining -= TCPOLEN_SACKPERM_ALIGNED;
    }
    if (foc != NULL && foc->len >= 0) {
        u32 need = foc->len;

        need += foc->exp ? TCPOLEN_EXP_FASTOPEN_BASE :
                   TCPOLEN_FASTOPEN_BASE;
        need = (need + 3) & ~3U;  /* Align to 32 bits */
        if (remaining >= need) {
            opts->options |= OPTION_FAST_OPEN_COOKIE;
            opts->fastopen_cookie = foc;
            remaining -= need;
        }
    }

    return MAX_TCP_OPTION_SPACE - remaining;/* TCP选项长度 */

 server端TCP在收到SYN请求后进行的处理总结如下:
1、创建一个比较小的数据结构request_sock并保存连接信息
2、将request_sock加入到syn table中,以便ACK到来时能够找到相应的连接信息
3、创建SYN|ACK包并发送出去,同时设置重传定时器以避免SYN|ACK包丢失



posted @ 2019-07-07 19:56  codestacklinuxer  阅读(765)  评论(0编辑  收藏  举报