udp bind && udp socket如何被访问

之前也聊过udp:udp dns 的思考

UDP 传输块的管理

1、udp并不是在hash 接口中将其控制块添加到udp_hash散列表中,而是在绑定端口后才将其添加到散列表中;

2、并不是所有的udp传输控制块都在散列表中管理,只有当套接字绑定了端口之后,此时可以接收发送数据,才会添加到散列表中管理

3、udp_hash为散列表。socket 一旦绑定port就回添加到散列表管理,知道关闭后才会从散列表中删除

udp的bind 调用:

1、udp/tcp socket 执行bind时 首先调用inet_bind系统调用,如果是raw socket 则会调用对应proto bind 接口函数,目前tcp/udp socket 都是通过get_port 函数来bind socket

分析get_port函数

int udp_v4_get_port(struct sock *sk, unsigned short snum)
{
    unsigned int hash2_nulladdr =
        udp4_portaddr_hash(sock_net(sk), htonl(INADDR_ANY), snum);
    unsigned int hash2_partial =
        udp4_portaddr_hash(sock_net(sk), inet_sk(sk)->inet_rcv_saddr, 0);
/*
哈希值hash2_nulladdr由[INADDR_ANY, snum]得到,hash2_partial由[inet_rcv_saddr, 0]得到,

即前者用本地端口作哈希,后者用本地地址作哈希*/
    /* precompute partial secondary hash */
    udp_sk(sk)->udp_portaddr_hash = hash2_partial;
//ipv4_rcv_saddr_equal()是比较地址是否相等的函数
    return udp_lib_get_port(sk, snum, ipv4_rcv_saddr_equal, hash2_nulladdr);
}
/**
 *  udp_lib_get_port  -  UDP/-Lite port lookup for IPv4 and IPv6
 *
 *  @sk:          socket struct in question
 *  @snum:        port number to look up
 *  @saddr_comp:  AF-dependent comparison of bound local IP addresses
 *  @hash2_nulladdr: AF-dependent hash value in secondary hash chains,
 *                   with NULL address
 */
int udp_lib_get_port(struct sock *sk, unsigned short snum,
             int (*saddr_comp)(const struct sock *sk1,
                       const struct sock *sk2),
             unsigned int hash2_nulladdr)
{
    struct udp_hslot *hslot, *hslot2;
    struct udp_table *udptable = sk->sk_prot->h.udp_table;
    int    error = 1;
    struct net *net = sock_net(sk);

    if (!snum) {//num为0则先选择一个可用端口号,再插入//没有绑定本地端口
        int low, high, remaining;
        unsigned int rand;
        unsigned short first, last;
        DECLARE_BITMAP(bitmap, PORTS_PER_CHAIN);

        inet_get_local_port_range(net, &low, &high);
        remaining = (high - low) + 1;

        rand = prandom_u32();
        first = reciprocal_scale(rand, remaining) + low;
        /*
         * force rand to be an odd multiple of UDP_HTABLE_SIZE
         
         static inline u32 udp_hashfn(const struct net *net, u32 num, u32 mask)
         {
             return (num + net_hash_mix(net)) & mask;
         }//net_hash_mix(net)返回一般为0,hash公式可简写为num&mask。即本地端口对udptable大小取模
         */
        rand = (rand | 1) * (udptable->mask + 1);
        last = first + udptable->mask + 1;
        do {
            hslot = udp_hashslot(udptable, net, first);
            bitmap_zero(bitmap, PORTS_PER_CHAIN);
            spin_lock_bh(&hslot->lock);
            udp_lib_lport_inuse(net, snum, hslot, bitmap, sk,
                        saddr_comp, udptable->log);

            snum = first;
            /*
             * Iterate on all possible values of snum for this hash.
             * Using steps of an odd multiple of UDP_HTABLE_SIZE
             * give us randomization and full range coverage.
             */
            do {
                if (low <= snum && snum <= high &&
                    !test_bit(snum >> udptable->log, bitmap) &&
                    !inet_is_local_reserved_port(net, snum))
                    goto found;
                snum += rand;
            } while (snum != first);
            spin_unlock_bh(&hslot->lock);
        } while (++first != last);
        goto fail;
    } else {//snum不为0则先确定之前没有存储相应sk,再插入。
    //hslot是从udp_table中hash表取出的表项,键值是snum 端口号
        hslot = udp_hashslot(udptable, net, snum);
        spin_lock_bh(&hslot->lock);
        if (hslot->count > 10) {
            int exist;
            /*hslot->count大于10,即在hash表中以snum为键值的项的数目在于10,
            此时改用在hash2表中查找。
                        如果hslot->count不足10,那么直接在hash表中查找*/

        // 在之前udp_portaddr_hash 是rcv_addr xor过的数值
            unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum;

            slot2          &= udptable->mask;
            hash2_nulladdr &= udptable->mask;
           // hslot2是udptable中hash2表取出的表项,键值是[inet_rcv_addr, snum]
            hslot2 = udp_hashslot2(udptable, slot2);
            if (hslot->count < hslot2->count)
                goto scan_primary_hash;//hslot2项的数目比hslot还多,那么查找hash2表是不划算的 返回直接查找hash表
/*hslot2更少(这也是设计hash2的目的),使用udp_lib_lport_inuse2()查找是否有匹配项;
如果没有找到,则使用新的键值hash2_nulladdr,即[INADDR_ANY, snum]从hash2中取出表项,
再使用udp_lib_lport_inuse2()查找是否有匹配项。
如果有,表明要插入的sk已经存在于内核表中,直接返回;
如果没有,则执行sk的插入操作 */
            exist = udp_lib_lport_inuse2(net, snum, hslot2,
                             sk, saddr_comp);
            if (!exist && (hash2_nulladdr != slot2)) {
                hslot2 = udp_hashslot2(udptable, hash2_nulladdr);
                exist = udp_lib_lport_inuse2(net, snum, hslot2,
                                 sk, saddr_comp);
            }
            if (exist)
                goto fail_unlock;
            else
                goto found;
        }
scan_primary_hash:
        if (udp_lib_lport_inuse(net, snum, hslot, NULL, sk,
                    saddr_comp, 0))
            goto fail_unlock;
    }
found:
// 当没有在当前内核udp_table中找到匹配项时,执行插入新sk的操作
//给sk参数赋值:inet_num, udp_port_hash, udp_portaddr_hash。然后将sk加入到hash表和hash2表中,并增加相应计数
    inet_sk(sk)->inet_num = snum;
    udp_sk(sk)->udp_port_hash = snum;
    udp_sk(sk)->udp_portaddr_hash ^= snum;
    if (sk_unhashed(sk)) {
        sk_nulls_add_node_rcu(sk, &hslot->head);
        hslot->count++;
        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);

        hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash);
        spin_lock(&hslot2->lock);
        hlist_nulls_add_head_rcu(&udp_sk(sk)->udp_portaddr_node,
                     &hslot2->head);
        hslot2->count++;
        spin_unlock(&hslot2->lock);
    }
    error = 0;
fail_unlock:
    spin_unlock_bh(&hslot->lock);
fail:
    return error;
}

 

怎样判断bind 的这个port 是否可用? 

看下这个函数便可知:主要是判断 net_namespace 是否设置 reuseaddr  reuseport bind_dev_if 等标志位, notice只要有一个为false 就行, 

/*
 * Note: we still hold spinlock of primary hash chain, so no other writer
 * can insert/delete a socket with local_port == num
 */
static int udp_lib_lport_inuse2(struct net *net, __u16 num,
                struct udp_hslot *hslot2,
                struct sock *sk,
                int (*saddr_comp)(const struct sock *sk1,
                          const struct sock *sk2))
{
    struct sock *sk2;
    struct hlist_nulls_node *node;
    kuid_t uid = sock_i_uid(sk);
    int res = 0;

    spin_lock(&hslot2->lock);
    udp_portaddr_for_each_entry(sk2, node, &hslot2->head) {
        if (net_eq(sock_net(sk2), net) &&
            sk2 != sk &&
            (udp_sk(sk2)->udp_port_hash == num) &&
            (!sk2->sk_reuse || !sk->sk_reuse) &&
            (!sk2->sk_bound_dev_if || !sk->sk_bound_dev_if ||
             sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
            (!sk2->sk_reuseport || !sk->sk_reuseport ||
             !uid_eq(uid, sock_i_uid(sk2))) &&
            saddr_comp(sk, sk2)) {
            res = 1;
            break;
        }
    }
    spin_unlock(&hslot2->lock);
    return res;
}

 

sock如何被访问     

  创建的udp socket成功后,当使用该socket与外部通信时,协议栈会收到发往该socket的udp报文。      udp_rcv() -> __udp4_lib_rcv() -> __udp4_lib_lookup()      在该函数中有关于udp socket的查找代码段,它以[saddr, sport, daddr, dport, iif]为键值在udptable中查找相应的sk。

__udp4_lib_lookup() sock在udptable中查找
      查找的过程与插入sock的过程很相似,先以hnum作哈希得到hslot,daddr, hnum作哈希得到hslot2,如果hslot数目不足10或hslot的表项数少于hslot2的,则在hslot中查找(begin代码段)。否则,在hslot2中查找。查找时使用udp4_lib_lookup2()函数,它返回与收到报文相匹配的sock。

  在hslot2中没有查找结果,则用INADDR_ANY, hnum作哈希得到重新得到hslot2,因为服务器端的udp socket只绑定了本地端口,没有绑定本地地址,所以查找时需要先使用[saddr, sport]查找,没有时再使用[INADDR_ANY, sport]查找。如果hslot2->count比hslot->count要多,或者在hslot2中没有查找到,则在hslot中查找(begin代码段)

UDP 输入输出 函数调用:

http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
原文地址:https://www.cnblogs.com/codestack/p/15543265.html