zoukankan      html  css  js  c++  java
  • TCP层bind系统调用的实现分析

    说明:该文章中部分代码未能完全理解透彻,可能对您造成误解,请慎读;

    并建议您先阅读本博另外一篇文章:<Linux TCP套接字选项 之 SO_REUSEADDR && SO_REUSEPORT>

    另:该文章将会持续更新改进;

    TCP的接口绑定通过函数inet_csk_get_port函数执行,其中包含了未设置端口号自动分配的情况,设置了地址重用标记(SO_REUSEADDR)和设置了端口重用(SO_REUSEPORT)选项的处理;检查成功则控制块节点加入到绑定接口hash中对应端口的链表中;

      1 /* Obtain a reference to a local port for the given sock,
      2  * if snum is zero it means select any available local port.
      3  * We try to allocate an odd port (and leave even ports for connect())
      4  */
      5 /* 绑定端口bind */
      6 /* 下面的重用地址表示SO_REUSEADDR,重用端口表示SO_REUSEPORT */
      7 int inet_csk_get_port(struct sock *sk, unsigned short snum)
      8 {
      9     bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN;
     10     struct inet_hashinfo *hinfo = sk->sk_prot->h.hashinfo;
     11     int ret = 1, port = snum;
     12     struct inet_bind_hashbucket *head;
     13     struct net *net = sock_net(sk);
     14     struct inet_bind_bucket *tb = NULL;
     15     kuid_t uid = sock_i_uid(sk);
     16 
     17     /* 未设定端口,自动绑定 */
     18     if (!port) {
     19         /* 返回端口所在的桶节点 */
     20         head = inet_csk_find_open_port(sk, &tb, &port);
     21         /* 未找到桶节点 */
     22         if (!head)
     23             return ret;
     24         /* 没有相同节点,创建节点 */
     25         if (!tb)
     26             goto tb_not_found;
     27         
     28         /* 有相同节点,成功 */
     29         goto success;
     30     }
     31 
     32     /* 设置了端口,找到端口hash桶节点 */
     33     head = &hinfo->bhash[inet_bhashfn(net, port,
     34                       hinfo->bhash_size)];
     35     spin_lock_bh(&head->lock);
     36 
     37     /* 遍历该桶节点下面的所有端口 */
     38     inet_bind_bucket_for_each(tb, &head->chain)
     39         /* 找到相同端口 */
     40         if (net_eq(ib_net(tb), net) && tb->port == port)
     41             goto tb_found;
     42 tb_not_found:
     43     /* 创建绑定节点 */
     44     tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
     45                      net, head, port);
     46     if (!tb)
     47         goto fail_unlock;
     48 tb_found:
     49     /* 节点上有控制块 */
     50     if (!hlist_empty(&tb->owners)) {
     51         /* 强制绑定,成功 */
     52         if (sk->sk_reuse == SK_FORCE_REUSE)
     53             goto success;
     54 
     55         /* 快速的比对 */
     56         /* 已绑定的开启重用&&新请求绑定的开启了地址重用|| 端口重用检查通过,成功 */
     57         /* 先检查地址重用,未通过则检查端口重用 */
     58         if ((tb->fastreuse > 0 && reuse) ||
     59             sk_reuseport_match(tb, sk))
     60             goto success;
     61 
     62         /* 地址重用和端口重用fast检查都失败 */
     63 
     64         /* 走更精确的比对 */
     65         /* 注意,在指定端口的情况下,不需要进行严格检查,并且可以重用端口 */
     66         if (inet_csk_bind_conflict(sk, tb, true, true))
     67             goto fail_unlock;
     68     }
     69 success:
     70     /* 该节点有控制块共用 */
     71     if (!hlist_empty(&tb->owners)) {
     72         /* 设置地址重用标志 */
     73         tb->fastreuse = reuse;
     74 
     75         /* 如果开启端口重用 */
     76         if (sk->sk_reuseport) {
     77             tb->fastreuseport = FASTREUSEPORT_ANY;
     78             tb->fastuid = uid;
     79             tb->fast_rcv_saddr = sk->sk_rcv_saddr;
     80             tb->fast_ipv6_only = ipv6_only_sock(sk);
     81 #if IS_ENABLED(CONFIG_IPV6)
     82             tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
     83 #endif
     84         } 
     85         /* 未开启端口重用 */
     86         else {
     87             tb->fastreuseport = 0;
     88         }
     89     } 
     90     /* 该端口节点还没有其他控制块 */
     91     else {
     92         /* 未开启地址重用,置0 */
     93         if (!reuse)
     94             tb->fastreuse = 0;
     95 
     96         /* 开启了端口重用 */
     97         if (sk->sk_reuseport) {
     98             /* We didn't match or we don't have fastreuseport set on
     99              * the tb, but we have sk_reuseport set on this socket
    100              * and we know that there are no bind conflicts with
    101              * this socket in this tb, so reset our tb's reuseport
    102              * settings so that any subsequent sockets that match
    103              * our current socket will be put on the fast path.
    104              *
    105              * If we reset we need to set FASTREUSEPORT_STRICT so we
    106              * do extra checking for all subsequent sk_reuseport
    107              * socks.
    108              */
    109             if (!sk_reuseport_match(tb, sk)) {
    110                 tb->fastreuseport = FASTREUSEPORT_STRICT;
    111                 tb->fastuid = uid;
    112                 tb->fast_rcv_saddr = sk->sk_rcv_saddr;
    113                 tb->fast_ipv6_only = ipv6_only_sock(sk);
    114 #if IS_ENABLED(CONFIG_IPV6)
    115                 tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
    116 #endif
    117             }
    118         }
    119         /* 未开启端口重用 */
    120         else {
    121             tb->fastreuseport = 0;
    122         }
    123     }
    124 
    125     /* 添加到绑定hash */
    126     if (!inet_csk(sk)->icsk_bind_hash)
    127         inet_bind_hash(sk, tb, port);
    128     WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
    129     ret = 0;
    130 
    131 fail_unlock:
    132     spin_unlock_bh(&head->lock);
    133     return ret;
    134 }

    对于未设置端口号的绑定,系统会在端口号范围内查找一个没有冲突的端口号;

      1 /*
      2  * Find an open port number for the socket.  Returns with the
      3  * inet_bind_hashbucket lock held.
      4  */
      5 static struct inet_bind_hashbucket *
      6 inet_csk_find_open_port(struct sock *sk, struct inet_bind_bucket **tb_ret, int *port_ret)
      7 {
      8     struct inet_hashinfo *hinfo = sk->sk_prot->h.hashinfo;
      9     int port = 0;
     10     struct inet_bind_hashbucket *head;
     11     struct net *net = sock_net(sk);
     12     int i, low, high, attempt_half;
     13     struct inet_bind_bucket *tb;
     14     u32 remaining, offset;
     15 
     16     /* 地址重用则attempt_half设置为1 */
     17     attempt_half = (sk->sk_reuse == SK_CAN_REUSE) ? 1 : 0;
     18 other_half_scan:
     19     /* 获取端口范围区间 */
     20     inet_get_local_port_range(net, &low, &high);
     21     high++; /* [32768, 60999] -> [32768, 61000[ */
     22 
     23     /* 端口范围很小,attempt_half设置为0 */
     24     if (high - low < 4)
     25         attempt_half = 0;
     26     /* attempt_half不为0 */
     27     if (attempt_half) {
     28 
     29         /* 找到一半的位置 */
     30         int half = low + (((high - low) >> 2) << 1);
     31 
     32         /* 第一次尝试低一半 */
     33         if (attempt_half == 1)
     34             high = half;
     35         /* 否则尝试高一半 */
     36         else
     37             low = half;
     38     }
     39 
     40     /* 地址数 */
     41     remaining = high - low;
     42 
     43     /* 地址数消除低位 */
     44     if (likely(remaining > 1))
     45         remaining &= ~1U;
     46 
     47     /* 随机一个偏移 */
     48     offset = prandom_u32() % remaining;
     49     /* __inet_hash_connect() favors ports having @low parity
     50      * We do the opposite to not pollute connect() users.
     51      */
     52     /* 偏移低位置1 ,方便下面分半遍历端口*/
     53     offset |= 1U;
     54 
     55 other_parity_scan:
     56 
     57     /* 取一个端口 */
     58     port = low + offset;
     59 
     60     /* 遍历查找合适端口,先遍历一半端口 */
     61     for (i = 0; i < remaining; i += 2, port += 2) {
     62         if (unlikely(port >= high))
     63             port -= remaining;
     64 
     65         /* 如果端口配置为保留端口,继续下一个端口 */
     66         if (inet_is_local_reserved_port(net, port))
     67             continue;
     68 
     69         /* 找到该端口对应的绑定端口列表 */
     70         head = &hinfo->bhash[inet_bhashfn(net, port,
     71                           hinfo->bhash_size)];
     72         spin_lock_bh(&head->lock);
     73         /* 遍历该链表 */
     74         inet_bind_bucket_for_each(tb, &head->chain)
     75             /* 同一个命名空间&& 端口相同 */
     76             if (net_eq(ib_net(tb), net) && tb->port == port) {
     77                 /* 绑定无冲突,成功 */
     78                 /* 注意,随机端口,需要进行严谨的检查,并且不能使用端口重用 */
     79                 if (!inet_csk_bind_conflict(sk, tb, false, false))
     80                     goto success;
     81 
     82                 /* 有冲突,下一个端口 */
     83                 goto next_port;
     84             }
     85         /* 没有命名空间和端口相同,成功 */
     86         tb = NULL;
     87         goto success;
     88 next_port:
     89         spin_unlock_bh(&head->lock);
     90         cond_resched();
     91     }
     92 
     93     /* 遍历另外一半端口 */
     94     offset--;
     95     if (!(offset & 1))
     96         goto other_parity_scan;
     97 
     98     /* 端口均不能用,则尝试高位端口的一半 */
     99     if (attempt_half == 1) {
    100         /* OK we now try the upper half of the range */
    101         attempt_half = 2;
    102         goto other_half_scan;
    103     }
    104     return NULL;
    105 
    106     /* 成功返回端口和节点(有端口相同时不为NULL) */
    107 success:
    108     *port_ret = port;
    109     *tb_ret = tb;
    110     return head;
    111 }

    inet_csk_bind_conflict用来判断端口是否冲突;

     1 static int inet_csk_bind_conflict(const struct sock *sk,
     2                   const struct inet_bind_bucket *tb,
     3                   bool relax, bool reuseport_ok)
     4 {
     5     struct sock *sk2;
     6     /* 地址重用 */
     7     bool reuse = sk->sk_reuse;
     8     /* 端口重用 */
     9     bool reuseport = !!sk->sk_reuseport && reuseport_ok;
    10     /* 用户id */
    11     kuid_t uid = sock_i_uid((struct sock *)sk);
    12 
    13     /*
    14      * Unlike other sk lookup places we do not check
    15      * for sk_net here, since _all_ the socks listed
    16      * in tb->owners list belong to the same net - the
    17      * one this bucket belongs to.
    18      */
    19     /* 遍历所有绑定控制块 */
    20     sk_for_each_bound(sk2, &tb->owners) {
    21         if (sk != sk2 && /* 控制块不同 */
    22             /* 输出报文的网络接口号为0或者相等 */
    23             (!sk->sk_bound_dev_if || 
    24              !sk2->sk_bound_dev_if ||
    25              sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) {
    26 
    27             /* 或逻辑好多,看着晕啊,看看什么情况下不需要判断吧 */
    28 
    29                /*
    30                           未启用地址重用 && 未启用端口重用:检查冲突;
    31                           启用了地址重用 && 未启用端口重用:状态是LISTEN才检查冲突;
    32                           未启用地址重用 && 启用了端口重用:状态不是TIME_WAIT并且不是同一有效用户ID时,检查冲突;
    33                                                          也就是说,假若是TIME_WAIT,则不需要检查;
    34                                                          假如不是TIME_WAIT,但是有效用户ID相同,也不需要检查;
    35 
    36                           启用了地址重用 && 启用了端口重用:状态是LISTEN时,可能需要检查,需要继续判断端口重用,
    37                                                           这时候只当有效用户ID不相同的时候,才需要检查;
    38                                                           就是说,可以相同用户ID的进程可以同时LISTEN多个相同的地址+端口;
    39                         */
    40             
    41             if ((!reuse || !sk2->sk_reuse ||
    42                 sk2->sk_state == TCP_LISTEN) &&
    43                 (!reuseport || !sk2->sk_reuseport ||
    44                  rcu_access_pointer(sk->sk_reuseport_cb) ||
    45                  (sk2->sk_state != TCP_TIME_WAIT &&
    46                  !uid_eq(uid, sock_i_uid(sk2))))) {
    47 
    48                 /* 地址相同,冲突 */
    49                 if (inet_rcv_saddr_equal(sk, sk2, true))
    50                     break;
    51             }
    52 
    53             /* 上面不需要判断的走到这里的情况 */
    54             /* 情况1.新旧绑定都设置了地址重用,状态不是LISTEN ,不满足本条,继续下面2*/
    55             /* 情况2.新旧绑定都设置了端口重用,状态是TIME_WAIT或者用户ID相等 */
    56 
    57 
    58             /* 上面1情况如果不放宽检查,则检查 */
    59             if (!relax && reuse && sk2->sk_reuse &&
    60                 sk2->sk_state != TCP_LISTEN) {
    61 
    62                 /* 地址相同,冲突 */
    63                 if (inet_rcv_saddr_equal(sk, sk2, true))
    64                     break;
    65             }
    66         }
    67     }
    68     return sk2 != NULL;
    69 }
  • 相关阅读:
    java进度条
    获取程序运行环境
    struts2学习笔记(二) 初识Struts2
    HttpComponents入门解析
    C#编码规范
    js实现GBK编码
    struts2学习笔记(一) MVC模式
    mysql数据库操作类
    java类装载器原理
    [Study Note] NHibernate in Action 20100729
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11750510.html
Copyright © 2011-2022 走看看