zoukankan      html  css  js  c++  java
  • 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的胖子
  • 相关阅读:
    Java学习笔记day01
    对有序数组进行二分查找(折半查找)
    对数组进行冒泡排序
    LeetCode #344. Reverse String
    LeetCode #292. Nim Game
    LeetCode #258. Add Digits
    Android DiskLruCache完全解析,硬盘缓存的最佳方案
    Android源码解析——LruCache
    Messenger与AIDL的异同
    Android应用层View绘制流程与源码分析
  • 原文地址:https://www.cnblogs.com/codestack/p/15543265.html
Copyright © 2011-2022 走看看