• TCP连接建立系列 — TCP选项解析


    本文主要分析:在收到客户端的SYN包时,服务器端是如何解析它所携带的TCP选项,并结合本端情况决定是否予以支持。

    内核版本:3.6

    Author:zhangskd @ csdn blog

    概述

    收到客户端的SYN包时,需要全面的解析它携带的TCP选项,这样我们就知道客户端支持哪些选项,如果本端也支持,

    那么连接就支持这些TCP选项。这些信息在连接建立的过程中,是保存在连接请求块的(request_sock、inet_request_sock、

    tcp_request_sock)。

    函数调用路径:

    tcp_v4_conn_request

            |--> tcp_parse_options

    3.6版本Linux内核支持的TCP选项包括:

    NOP,用于填充。

    Max Segment Size,最大分段大小。

    Window Scaling,窗口扩大因子。

    SACK Permit,是否允许使用SACK。

    SACK,携带SACK块。

    Timestamp,时间戳选项。

    MD5SIG,MD5签名。

    Cookie,Cookie extension,2013年3月已从内核移除。

    EXP,Experimental,处于实验阶段的选项,新版本用于Fast Open。

    实现

    全面解析数据包的TCP选项,并保存到指定的tcp_options_received实例中。

    /* 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 sk_buff *skb, struct tcp_options_received *opt_rx, const u8 **hvpp,
                           int estab, struct tcp_fastopen_cookie *foc)
    {
        const unsigned char *ptr;
        const struct tcphdr *th = tcp_hdr(skb);
        int length = (th->doff * 4) - sizeof(struct tcphdr); /* 选项的总长度 */
    
        ptr = (const unsigned char *) (th + 1); /* 选项的起始地址 */
        opt_rx->saw_tstamp = 0; /* Saw TIMESTAMP on last packet */
    
        while(length > 0) {
            int opcode = *ptr++; /* 选项kind */
            int opsize; /* 选项length */
    
            switch(opcode) {
            case TCPOPT_EOL: /* End of options */
                return; 
    
            case TCPOPT_NOP: /* Padding,填充选项 */
                length--; /* 此选项只占一个字节 */
                continue;
    
            default:
                opsize = *ptr++; /* 选项长度 */
                if (opsize < 2) /* silly options */
                    return; /* 选项长度过小 */
    
                if (opsize > length)
                    return; /* don't parse partial options */
    
                switch(opcode) {
                case TCPOPT_MSS: /* MSS选项 */
                    if (opsize == TCPOLEN_MSS && th->syn && ! estab) {
                        u16 in_mss = get_unaligned_be16(ptr);
    
                        if (in_mss) {
                            /* 如果用户指定了MSS,且比对端通告的小 */
                            if (opt_rx->user_mss && opt_rx->user_mss < in_mss)
                                in_mss = opt_rx->user_mss;
    
                            opt_rx->mss_clamp = in_mss; /* Maximal mss */
                        }
                    }
                    break;
    
                case TCPOPT_WINDOW: /* Window scaling */
                    if (opsize == TCPOLEN_WINDOW && th->syn && ! estab &&
                        sysctl_tcp_window_scaling) {
                        __u8 snd_wscale = *(__u8 *)ptr; /* 对端的接收窗口扩大因子 */
                        opt_rx->wscale_ok = 1; /* 连接使用窗口扩大选项 */
    
                        if (snd_wscale > 14) {
                            net_info_ratelimited("%s: Illegal window scaling value %d >14 received
    ",
                                   __func__, snd_wscale);
                            snd_wscale = 14;
                        }
    
                        opt_rx->snd_wscale = snd_wscale; /* 保存对端的接收窗口扩大因子 */
                    }
                    break;
    
                case TCPOPT_TIMESTAMP: /* Better RTT estimations/PAWS */
                    if ((opsize == TCPOLEN_TIMESTAMP) && ((estab && opt_rx->tstamp_ok) ||
                         (!estab && sysctl_tcp_timestamps))) {
    
                        opt_rx->saw_tstamp = 1; /* 连接支持TIMESTAMP选项 */
                        opt_rx->rcv_tsval = get_unaligned_be32(ptr); /* Time stamp value */
                        opt_rx->rcv_tsecr = get_unaligned_be32(ptr + 4); /* Time stamp echo reply */
                    }
                    break;
    
                case TCPOPT_SACK_PERM: /* SACK Permitted */
                    if (opsize == TCPOLEN_SACK_PERM && th->syn && !estab && sysctl_tcp_sack) {
                        opt_rx->sack_ok = TCP_SACK_SEEN; /* 连接支持SACK选项 */
                        tcp_sack_reset(opt_rx); /* 清空dsack和num_sacks变量 */
                    }
                    break;
    
                case TCPOPT_SACK: /* SACK Block */
                    if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) &&
                        !((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) && opt_rx->sack_ok) {
                        /* 保存SACK选项的起始偏移地址 */
                        TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *) th; 
                    }
                    break;
    
    #ifdef CONFIG_TCP_MD5SIG
                case TCPOPT_MD5SIG:
                    /* The MD5 Hash has already been checked
                     * (see tcp_v{4, 6}_do_rcv()).
                     */
                    break;
    #endif
    
                /* 注意,在新版内核中此选项已被删除。Cookie extension (experimental) */
                case TCPOPT_COOKIE: 
                    /* This option is variable length. */
                    switch(opsize) {
                    case TCPOLEN_COOKIE_BASE:
                        /* not yet implemented */
                        break;
    
                    case TCPOLEN_COOKIE_PAIR:
                        /* not yet implemented */
                        break;
    
                    case TCPOLEN_COOKIE_MIN + 0:
                    case TCPOLEN_COOKIE_MIN + 2:
                    case TCPOLEN_COOKIE_MIN + 4:
                    case TCPOLEN_COOKIE_MIN + 6:
                    case TCPOLEN_COOKIE_MAX:
    
                        /* 16-bit multple */
                        opt_rx->cookie_plus = opsize;
                        *hvpp = ptr;
                        break;
    
                    default:
                        /* ignore option */
                        break;
                    }
                    break;
     
                case TCPOPT_EXP: /* Experimental */
                    /* Fast Open option shares code 254 using a 16 bits magic number.
                     * It's valid only in SYN or SYN-ACK with an even size.
                     */
                    if (opsize < TCPOLEN_EXP_FASTOPEN_BASE || get_unaligned_be16(ptr) != TCPOPT_FASTOPEN_MAGIC
                        || foc == NULL || ! th->syn || (opsize & 1))
                        break;
    
                    foc->len = opsize - TCPOLEN_EXP_FASTOPEN_BASE;
                    if (foc->len >= TCP_FASTOPEN_COOKIE_MIN && foc->len <= TCP_FASTOPEN_COOKIE_MAX)
                        memcpy(foc->val, ptr + 2, foc->len);
                    else if (foc->len != 0)
                        foc->len = -1;
                    break;
                }
    
                ptr += opsize - 2;
                length -= opsize;
            }
        }
    }
    /* TCP options */
    #define TCPOPT_NOP 1 /* Padding */
    #define TCPOPT_EOL 0 /* End of options */
    #define TCPOPT_MSS 2 /* Segment size negotiating */
    #define TCPOPT_WINDOW 3 /* Window scaling */
    #define TCPOPT_TIMESTAMP 8 /* Better RTT estimations/PAWS */
    #define TCPOPT_SACK_PERM 4 /* SACK Permitted */
    #define TCPOPT_SACK 5 /* SACK Block */
    #define TCPOPT_MD5SIG 19 /* MD5 Signature (RFC2385) */
    #define TCPOPT_COOKIE 253 /* Cookie extension (experimental) */
    #define TCPOPT_EXP 254 /* Experimental */
    
    /* Magic number to be after the option value for sharing TCP experimental options.
     * See draft-ietf-tcpm-experimental-options-00.txt
     */
    #define TCPOPT_FASTOPEN_MAGIC 0xF989
    
    /* TCP option lengths */
    #define TCPOLEN_MSS 4
    #define TCPOLEN_WINDOW 3
    #define TCPOLEN_TIMESTAMP 10
    #define TCPOLEN_SACK_PERM 2
    #define TCPOLEN_COOKIE_BASE 2 /* Cookie-less header extension */
    #define TCPOLEN_COOKIE_PAIR 3 /* Cookie pair header extension */
    #define TCPOLEN_COOKIE_MIN (TCPOLEN_COOKIE_BASE + TCP_COOKIE_MIN)
    #define TCPOLEN_COOKIE_MAX (TCPOLEN_COOKIE_BASE + TCP_COOKIE_MAX)
    #define TCPOLEN_EXP_FASTOPEN_BASE 4
    
    /* These are used to set the sack_ok field in struct tcp_options_received */
    #define TCP_SACK_SEEN (1 << 0) /* peer is SACK capable */
    #define TCP_FACK_ENABLED (1 << 1) /* FACK is enabled locally */
    #define TCP_DSACK_SEEN (1 << 2) /* DSACK was received from peer */
    
    /* for TCP_COOKIE_TRANSACTIONS (TCPCT) socket option */
    #define TCP_COOKIE_MIN 8 /* 64-bits */
    #define TCP_COOKIE_MAX 16 /* 128-bits */
    
    /* TCP Fast Open */
    #define TCP_FASTOPEN_COOKIE_MIN 4 /* Min Fast Open Cookie size in bytes */
    #define TCP_FASTOPEN_COOKIE_MAX 16 /* Max Fast Open Cookie size in bytes */
    
    /* TCP Fast Open Cookie as stored in memory */
    struct tcp_fastopen_cookie {
        s8 len;
        u8 val[TCP_FASTOPEN_COOKIE_MAX];
    };
    
    static inline void tcp_sack_reset(struct tcp_options_received *rx_opt)
    {
        rx_opt->dsack = 0;
        rx_opt->num_sacks = 0;
    }
    

    用于判断连接是否使用ECN。要注意的是在建立连接时,要求IP报的ECN域不能被设置,否则就禁用ECN。

    /* RFC3168: 6.1.1 SYN packets must not have ECT/ECN bits set.
     *
     * If we receive a SYN packet with these bits set, it means a network is playing  bad games
     * with TOS bits. In order to avoid possible false congestion notifications, we disable
     * TCP ECN negociation.
     */
    static inline void TCP_ECN_create_request(struct request_sock *req, const struct sk_buff *skb)
    {
        const struct tcphdr *th = tcp_hdr(skb);
    
        if (sysctl_tcp_ecn && th->ece && th->cwr && INET_ECN_is_not_ect(TCP_SKB_CB(skb)->ip_dsfield))
            inet_rsk(req)->ecn_ok = 1; /* 连接支持ECN */
    }
    

    清零TCP选项。

    static inline void tcp_clear_options(struct tcp_options_received *rx_opt)
    {
        rx_opt->tstamp_ok = rx_opt->sack_ok = 0;
        rx_opt->wscale_ok = rx_opt->snd_wscale = 0;
        rx_opt->cookie_plus = 0;
    }
    
  • 相关阅读:
    cocos2d tiledmap
    cocos2d 例子编辑器
    cocos2d 粒子系统
    【leetcode】矩阵中的幸运数
    【leetcode】魔术索引
    【leetcode】多数元素
    【leetcode】整理字符串
    【leetcode】通过翻转子数组使两个数组相等
    【leetcode】珠玑妙算
    【leetcode】距离顺序排列矩阵单元格
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333311.html
Copyright © 2020-2023  润新知