首页 > 其他 > 详细

对udp dns的一次思考

时间:2020-04-25 20:25:44      阅读:66      评论:0      收藏:0      [点我收藏+]

  目前昨天查一个线上问题:“”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。其中 hash 以 port 为 key,hash2 以 IP+port (port=0)为 key

 

  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) {
 13         int low, high, remaining;
 14         unsigned int rand;
 15         unsigned short first, last;
 16         DECLARE_BITMAP(bitmap, PORTS_PER_CHAIN);
 17 
 18         inet_get_local_port_range(net, &low, &high);
 19         remaining = (high - low) + 1;
 20 
 21         rand = prandom_u32();
 22         first = reciprocal_scale(rand, remaining) + low;
 23         /*
 24          * force rand to be an odd multiple of UDP_HTABLE_SIZE
 25          */
 26         rand = (rand | 1) * (udptable->mask + 1);
 27         last = first + udptable->mask + 1;
 28         do {
 29             hslot = udp_hashslot(udptable, net, first);
 30             bitmap_zero(bitmap, PORTS_PER_CHAIN);
 31             spin_lock_bh(&hslot->lock);
 32             udp_lib_lport_inuse(net, snum, hslot, bitmap, sk,
 33                         saddr_comp, udptable->log);
 34 
 35             snum = first;
 36             /*
 37              * Iterate on all possible values of snum for this hash.
 38              * Using steps of an odd multiple of UDP_HTABLE_SIZE
 39              * give us randomization and full range coverage.
 40              */
 41             do {
 42                 if (low <= snum && snum <= high &&
 43                     !test_bit(snum >> udptable->log, bitmap) &&
 44                     !inet_is_local_reserved_port(net, snum))
 45                     goto found;
 46                 snum += rand;
 47             } while (snum != first);
 48             spin_unlock_bh(&hslot->lock);
 49         } while (++first != last);
 50         goto fail;
 51     } else {
 52         hslot = udp_hashslot(udptable, net, snum);
 53         spin_lock_bh(&hslot->lock);
 54         if (hslot->count > 10) {
 55             int exist;
 56             unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum;
 57 
 58             slot2          &= udptable->mask;
 59             hash2_nulladdr &= udptable->mask;
 60 
 61             hslot2 = udp_hashslot2(udptable, slot2);
 62             if (hslot->count < hslot2->count)
 63                 goto scan_primary_hash;
 64 
 65             exist = udp_lib_lport_inuse2(net, snum, hslot2,
 66                              sk, saddr_comp);
 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);
 71             }
 72             if (exist)
 73                 goto fail_unlock;
 74             else
 75                 goto found;
 76         }
 77 scan_primary_hash:
 78         if (udp_lib_lport_inuse(net, snum, hslot, NULL, sk,
 79                     saddr_comp, 0))
 80             goto fail_unlock;
 81     }
 82 found:
 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 }

 

 以bind时:port不为0即port已经选定进行分析;

 

对udp dns的一次思考

原文:https://www.cnblogs.com/codestack/p/12774946.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!