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 输入输出 函数调用:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
2020-11-12 Why use MSIX message signal interrupt
2019-11-12 邻居子系统1.5 neigh output
2019-11-12 netfilter内核态与用户态 通信 之 sockopt