目前昨天查一个线上问题:“”dns服务器在我们的设备, 有大量的终端到设备上请求解析域名,但是一直是单线程,dns报文处理不过来”, 然而设备是多核,dns服务器一直不能利用多核资源,所以能不能使用多线程进行处理呢?

  udp不像tcp那样,udp没有连接的概念,也就是没有通过建立多个连接来提高对dns服务器并发访问,然而在多核环境下那就只能通过多线程来访问一个共享的udp socket,但是还是一个socket , 会涉及到多线程抢占资源问题。

  来看一下内核协议栈udp收到包代码:根据以前分析tcpip协议栈文章可以知道,报文在内核协议栈流程大约如下:

  •   netif_receive_skb
  •   pt_prev->func(skb, skb->dev, pt_prev, orig_dev);   调用ip_rcv  arp_rcv处理
  •         ip_rcv  ------- ip_rcv_finish
  •         根据路由选项是否local_input还是ip_forward准发
  •        ip_local_deliver(期间会涉及到ip 分片重组)
  •        ip_local_deliver_finish   (涉及到raw socket 收包问题)------>ret = ipprot->handler(skb);//这里面会进入udp tcp传输层
  •      udp_rcv  //收发包处理位置----->__udp4_lib_rcv
  •    sk = __udp4_lib_lookup; ////根据报文的端口号和目的端口号查询udptable,寻找应该接收该数据包的传输控制块
  •    udp_queue_rcv_skb//涉及到内核态和 用户态抢占socket的问题, 所以有收包队列 以及 sk_add_backlog队列
  •   sk->sk_data_ready(sk, skb_len) wake up 唤醒等待队列,
  •    如果找不到对应监听socket 则icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); 回复不可达 这个和tcp回复的rst不一样!!!!

也就是 报文送到那个socket是由__udp4_lib_lookup这个函数做出选择, 选择的依据是 ip 端口号 接口来进行处理选择对应的socket,对于

dhcp dns服务器来说一般不会绑定接口,所以一般就是 设置ip  udp.port, 所以内核选择socket的时候一般也是通过对比ip port来查找。通过找出匹配层度最高的socket作为收包sk。

如果要是允许有多个socket呢??

那么不就是可以通过轮询选择或者hash选择出对应的socket 吗!!!!!!!! 所以在linux 3.9内核版本后面增加reuseport,允许多个socket绑定同一个ip port, 通过hash散列在桶里面,

后面就允许多线程/多进程服务器的每个线程都bind 同一个端口,并且最终每个线程拥有一个独立的socket,而不是所有线程都访问一个socket;没有reuseport这个patch的话,这么做的后果就是服务器会报出一个类似“地址/端口被占用的”错误信息。

socket  bind ip port时 会调用get_port  计算是否存在ip port存在冲突, linux 3.9patch中对hash 以及计算方式加入reuseport,可以允许多个socket bind同样的ip port。

同一个客户端的数据总是分配给同一个 udp_sock。so!! 在写 UDP server 的时候,为了提高处理能力,可以起多个线程,每个线程读写自己的 UDP socket

顺便看一看udp 怎样查找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); /* precompute partial secondary hash */
udp_sk(sk)->udp_portaddr_hash = hash2_partial;
return udp_lib_get_port(sk, snum, ipv4_rcv_saddr_equal, hash2_nulladdr);
}

UDP 对于porttable维护一个是一port 进行hash  一个是以sip +port(port=0) 进行hash。

struct udp_sock {
    /* inet_sock has to be the first member */
    struct inet_sock inet;
#define udp_port_hash        inet.sk.__sk_common.skc_u16hashes[0]
#define udp_portaddr_hash    inet.sk.__sk_common.skc_u16hashes[1]

}

UDP 协议的主要数据结构是两张 hash 表,指向 UDP 协议控制块 struct udp_sock。其中 hash1 以 port 为 key,

hash2 以 IP+port (port=0)为 key 但是后续会使用udp_portaddr_hash  ^port 进行hash查找,实际上也就是ip+port表 ;

所以一开始看的udp_portaddr_hash 是以ip+port=0 进行hash计算有点懵逼!!!

  1 int udp_lib_get_port(struct sock *sk, unsigned short snum,
2 int (*saddr_comp)(const struct sock *sk1,
3 const struct sock *sk2,
4 bool match_wildcard),
5 unsigned int hash2_nulladdr)
6 {
7 struct udp_hslot *hslot, *hslot2;
8 struct udp_table *udptable = sk->sk_prot->h.udp_table;
9 int error = 1;
10 struct net *net = sock_net(sk);
11
12 if (!snum) {
     ............................
........................... 51 } else {
//以portnum 为key查找散列表
52 hslot = udp_hashslot(udptable, net, snum);
53 spin_lock_bh(&hslot->lock);
54 if (hslot->count > 10) {//当端口hash表冲突链长度大于10时,启用二元组hash查询
55 int exist;
56 unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum;//相当于ip+port 选择key值
57
58 slot2 &= udptable->mask;
59 hash2_nulladdr &= udptable->mask;
60
61 hslot2 = udp_hashslot2(udptable, slot2);
62 if (hslot->count < hslot2->count)//如果hslot2项的冲突数目比hslot还多,那么查找hash2表inuse2是不划算的,返回直接查找hash1表inuse
63 goto scan_primary_hash;
64
65 exist = udp_lib_lport_inuse2(net, snum, hslot2,
66 sk, saddr_comp);//使用udp_lib_lport_inuse2()查找是否有匹配项;如果没有找到,则使用新的键值hash2_nulladdr,即[INADDR_ANY, snum]从hash2中取出表项
67 if (!exist && (hash2_nulladdr != slot2)) {
68 hslot2 = udp_hashslot2(udptable, hash2_nulladdr);
69 exist = udp_lib_lport_inuse2(net, snum, hslot2,
70 sk, saddr_comp);//再使用udp_lib_lport_inuse2()查找是否有匹配
71 }
72 if (exist)
73 goto fail_unlock;
74 else
75 goto found;
76 }
77 scan_primary_hash://scan_primary_hash代码段是在hash表的hslot项中查找,只有当在hash2中查找更费时时才会执行
78 if (udp_lib_lport_inuse(net, snum, hslot, NULL, sk,
79 saddr_comp, 0))
80 goto fail_unlock;
81 }
82 found://执行sk的插入操作
83 inet_sk(sk)->inet_num = snum;
84 udp_sk(sk)->udp_port_hash = snum;
85 udp_sk(sk)->udp_portaddr_hash ^= snum;
86 if (sk_unhashed(sk)) {
87 if (sk->sk_reuseport &&
88 udp_reuseport_add_sock(sk, hslot, saddr_comp)) {
89 inet_sk(sk)->inet_num = 0;
90 udp_sk(sk)->udp_port_hash = 0;
91 udp_sk(sk)->udp_portaddr_hash ^= snum;
92 goto fail_unlock;
93 }
94
95 sk_add_node_rcu(sk, &hslot->head);
96 hslot->count++;
97 sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
98
99 hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash);
100 spin_lock(&hslot2->lock);
101 if (IS_ENABLED(CONFIG_IPV6) && sk->sk_reuseport &&
102 sk->sk_family == AF_INET6)
103 hlist_add_tail_rcu(&udp_sk(sk)->udp_portaddr_node,
104 &hslot2->head);
105 else
106 hlist_add_head_rcu(&udp_sk(sk)->udp_portaddr_node,
107 &hslot2->head);
108 hslot2->count++;
109 spin_unlock(&hslot2->lock);
110 }
111 sock_set_flag(sk, SOCK_RCU_FREE);
112 error = 0;
113 fail_unlock:
114 spin_unlock_bh(&hslot->lock);
115 fail:
116 return error;
117 }

 如果snum==0,即没有绑定本地端口,此时执行if部分代码段,这种情况一般发生在客户端使用socket,此时内核会为它选择一个未使用的端口:

udptable中的hash公司为 jhash_1word((__force u32)saddr, net_hash_mix(net)) ^ port------>(num + net_hash_mix(net)) & mask简写一下, net_hash_mix(net)返回为0

所以大约就是sip^port; 具体就不细看hash函数了。也许不是这样的。。。。

if (!snum) {
/*
如果snum==0,即没有绑定本地端口,此时执行if部分代码段,这种情况一般发生在客户端使用socket,
此时内核会为它选择一个未使用的端口: udptable中的hash公司为 jhash_1word((__force u32)saddr, net_hash_mix(net)) ^ port---
--->(num + net_hash_mix(net)) & mask简写一下, net_hash_mix(net)返回为0 所以大约就是sip^port; 具体就不细看hash函数了。也许不是这样的。。。。 声明bitmap数组,大小为udp_table每个键值最多存储的表项,即最大端口号/哈希表大小。
端口号的值规定范围是1-65536,而哈希表一般大小是256,因此实际分配bitmap[8]。
low和high代表可用本地端口的下限和上限;remaining代表位于low和high间的端口号数目。
用随机值rand生成first,注意它是unsigned short类型,16位,表示起始查找位置;last表示终止查找位置,
first和last相差表大小保证了所有键值都会被查询一次(表的大小为偶数,所以用奇数防止落在同一个桶里面)。
随机值rand最后处理成哈希表大小的奇数倍,
之所以要是奇数倍,是为了保证哈希到同一个键值的所有端口号都能被遍历,
可以试着1开始,每次+2和每次+3,直到回到1,所遍历的数有哪些不同,
*/
int low, high, remaining;
unsigned int rand;
unsigned short first, last;
//hash表达小一般是256
DECLARE_BITMAP(bitmap, PORTS_PER_CHAIN);//PORTS_PER_CHAIN (MAX_UDP_PORTS / UDP_HTABLE_SIZE_MIN)----65536/256 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
*/
rand = (rand | 1) * (udptable->mask + 1);
last = first + udptable->mask + 1;
do {/* 使用first值作为端口号,从udptable的hash表中找到hslot项,重置bitmap数组全0,
调用函数udp_lib_lport_inuse()遍历hslot项的所有表项,将所有已经使用的sport对应于bitmap的位置置1。*/
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.
此时bitmap中包含了所有哈希到hslot的端口的使用情况,下面要做的就是从first位置开始,
每次递增rand(保证哈希值不变),查找符合条件的端口:端口在low~high的可用范围内;
端口还没有被占用。
do{}while循环的判断条件snum!=first和snum+=rand一起保证了所有哈希到hslot的端口号都会被遍历到。
如果找到了可用端口号,即跳出,执行插入sk的操作,否则++first,
查找下一个键值,直到fisrt==last,表明所有键值都已轮循一遍,仍没有结果,则退出
*/
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;

这部分是查找网上分析结果!!还没有仔细研究他的这个hash算法

问题2:由于udp是包模式!! 每次只能copy一个包!!!能不能copy多个!!!目前是可以的!!

可以使用recvmmsg来继续降低系统调用的开销。recvmmsg是一个批量接口,它可以从socket里一次读出多个udp数据包,不像recvfrom那样一次只能读一个。如果客户端多、请求量大的话,recvmmsg的批量读就很有优势了。

就像hash表一样存在冲突!!! 也就是读取的多个包可能不是一个客户端发过来的数据!!!! 那怎样区分多个客户端发来的数据呢??在udp数据部分来区分??还是??

看内核代码应该是通过rcvmmsg的控制信息区分。。。。。读取数据包文的时候也会带上控制信息这样就可以知道对端了

对udp dns的一次思考的更多相关文章

  1. 对udp dns的思考2

    上一篇文章写道了udp 使用reuseport 多线程编程!!! 但是有几个问题需要考虑一下: 之前hash使用sip sport dip dport为key, 很正常同一个客户端回hash到同一个s ...

  2. nodejs的某些api~(四)udp&dns

    今天记udp/数据报套接字和dns. udp UDP/数据报套接字 => require('dgram');dgram.createServer([type],[cb]);type:可以是'ud ...

  3. 一次误报引发的DNS检测方案的思考:DNS隧道检测平民解决方案

    摘自:http://www.freebuf.com/articles/network/149328.html 通过以上分析得出监控需要关注的几个要素:长域名.频率.txt类型.终端是否对解析ip发起访 ...

  4. ss客户端以及tcp,udp,dns代理ss-tproxy本地安装版--centos7.3 x64以上(7.3-7.6x64测试通过)

    因为下载的文件,从cn下载很慢,或者下不动,所以我弄了一个本地安装版 本地安装的文件,我是从网上单独下载了,这里就不提供了. 记得在最后设置允许访问的局域网IP段 ## iptables 配置ipta ...

  5. ss客户端以及tcp,udp,dns代理ss-tproxy在线安装版--centos7.3 x64以上(7.3-7.6x64测试通过)

    #!/bin/sh # # Script for automatic setup of an SS-TPROXY server on CentOS 7.3 Minimal. # export PATH ...

  6. DNS分别在什么情况下使用UDP和TCP

    DNS同时占用UDP和TCP端口53是公认的,这种单个应用协议同时使用两种传输协议的情况在TCP/IP栈也算是个另类.但很少有人知道DNS分别在什么情况下使用这两种协议.     如果用wiresha ...

  7. DNS用的是TCP协议还是UDP协议

    DNS占用53号端口,同时使用TCP和UDP协议.那么DNS在什么情况下使用这两种协议? DNS在区域传输的时候使用TCP协议,其他时候使用UDP协议. DNS区域传输的时候使用TCP协议: 1.辅域 ...

  8. 没错,请求DNS服务器还可以使用UDP协议

    目录 简介 搭建netty客户端 在netty中发送DNS查询请求 DNS消息的处理 总结 简介 之前我们讲到了如何在netty中构建client向DNS服务器进行域名解析请求.使用的是最常见的TCP ...

  9. TCP/UDP端口列表

    http://zh.wikipedia.org/wiki/TCP/UDP%E7%AB%AF%E5%8F%A3%E5%88%97%E8%A1%A8 TCP/UDP端口列表     本条目可通过翻译外语维 ...

随机推荐

  1. Cypress系列(63)- 使用 Custom Commands

    如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html Custom Commands 自定义命 ...

  2. coder初入职场必备:Eclipse+Tomcat8+MAVEN+SVN 工作环境搭建

    1.JDK的安装 首先下载JDK,这个从sun公司官网可以下载,根据自己的系统选择64位还是32位,安装过程就是next一路到底.安装完成之后当然要配置环境变量了. ----------------- ...

  3. 多测师讲解接口测试 _postman(下)_高级讲师肖sir

    关联接口 定义:上个接口返回的参数作为下一个接口的入参 1)接口1:查询出所有的州,自治区,直辖市,省(且发送请求不需要入参) 接口url地址: http://www.webxml.com.cn/We ...

  4. 题解:SDOI2017 新生舞会

    题解:SDOI2017 新生舞会 Description 学校组织了一次新生舞会,Cathy 作为经验丰富的老学姐,负责为同学们安排舞伴. 有 \(n\) 个男生和 \(n\) 个女生参加舞会.一个男 ...

  5. day63 Pyhton 框架Django 06

    内容回顾 1.装饰器 装饰器:是一个闭包函数,在不改变原函数的代码和调用方式的基础上,给原函数增加功能. def wrapper(func): def inner(*args,**kwargs): # ...

  6. json对象去重,根据指定字段

    function FilterByName(data, Name) { //data是json对象,Name是根据什么字段去重 var map = {}, dest = []; for (var i ...

  7. 【原创】有利于提高xenomai 实时性的一些配置建议

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正. @ 目录 一.影响因素 1.硬件 2.BISO(X86平台) 3.软件 4. 缓存使用策略与GPU 二.优化措施 1. BIO ...

  8. centos8平台使用rpm管理软件包

    一,什么是rpm rpm是redhat package manager redhat的软件包管理器 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/a ...

  9. scrapy 采集数据存入excel

    # -*- coding: utf-8 -*- # Define your item pipelines here # # Don't forget to add your pipeline to t ...

  10. 使用creata-react-app脚手架创建react项目时非常慢的问题

    创建react项目必须要有下面两个步骤 cnpm install -g create-react-app  //创建react全局变量 create-react-app my-app //创建一个re ...