UDP socket查询高速缓存_udp_lib_unhash

UDP没有连接的概念,所以UDP不会保存 “正在和谁通信的信息” ,换句话说,UDP数据的发送是oneshot的。

我们来做个实验,两台机器分别部署UDP的server和client。

先看server:

#define PORT     2222
int main()
{
    int sockfd;
    char buffer[MAXLINE];
    char *tosend = "aaaaaaaaaaa";
    struct sockaddr_in servaddr, cliaddr;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("192.168.56.101");
    servaddr.sin_port = htons(PORT);

    bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr));

    n = recvfrom(sockfd, (char *)buffer, 64, MSG_WAITALL, ( struct sockaddr *) &cliaddr, &len);
    sendto(sockfd, (const char *)tosend, strlen(tosend), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len);

    return 0;
}

server逻辑很简单,接受client的一个buffer,然后回复一堆aaaaaaa…

再看client:

#define PORT     2222

int main()
{
    int sockfd;
    char buffer[MAXLINE];
    char *tosend = "bbbbbbbbbbbbb";
    struct sockaddr_in    servaddr;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = inet_addr("192.168.56.101");

    sendto(sockfd, (const char *)tosend, strlen(tosend), MSG_CONFIRM, (const struct sockaddr *) &servaddr, sizeof(servaddr));
    n = recvfrom(sockfd, (char *)buffer, 64, MSG_WAITALL, (struct sockaddr *) &servaddr, &len);
    printf("data: %s      %d\n", buffer, htons(servaddr.sin_port));

    return 0;
}

在client上配置iptables规则:

iptables -t raw -A INPUT -p udp -j NOTRAC
iptables -t nat -A INPUT -p udp --sport 2222 -j SNAT --to-source :12345

意思很简单,client给server的2222端口发送一堆b,然后server使用12345端口回复一堆a给client。作为iptables的替代,在recvfrom和sendto之间再次bind不同的address即可。

这肯定是可行的,数据是可以发送成功的。

  • 两个UDP socket互相通信,源端口是可以不断变化的。

UDP接收端在真的收到数据报文之前,是不知道源端口变化了的,按照UDP的规范,UDP socket在协议栈里不能以4元组来组织,而只能通过 {本地IP地址,本地端口} 来组织。

即便是connected UDP也不行。

connected UDP在查找时只是在compare时附加了源IP地址和源端口的检测。

这意味着一些高负载流式的UDP服务(用UDP传输流式数据,比如quic)在 连接数 (即唯一四元组的数量)很多时,socket无法通过四元组很好地进行散列,而只能退化成一条链表:

  • 服务端的端口与IP均相同。(相关的socket会hash到同一个bucket)
  • 客户端的端口与IP无法被利用(按照UDP语义和规范,它们‘可能’会发生变化)。

但是,可以为四元组加个缓存!

我们看Linux内核的实现,为了优化查询性能,在 目标端口 hash之外,引入了一个 {目标IP,目标端口} 二元组hash,当端口hash表冲突链长度大于10时,启用二元组hash查询。

然而,无济于事!

为了解决这个问题,加个四元组hash表查询缓存是一个正确的思路。缓存是规范外的。

我们可以看到,类似nf_conntrack,UDP防火墙等等均采用了 “UDP四元组作为连接性” 来优化查询性能的。我们看一个nf_conntrack的例子:

ipv4     2 udp      17 22 src=192.168.56.101 dst=192.168.56.110 sport=2222 dport=45069 [UNREPLIED] src=192.168.56.110 dst=192.168.56.101 sport=45069 dport=12345 mark=0 zone=0 use=2

实现这个代码非常简单,照猫画虎UDP socket组织的hash2即可,在hash2操作的地方,按照引入一个四元组hash3的对应操作即可:

  • udp_lib_get_port: 按照四元组插入hash3。
  • __udp4_lib_lookup_skb: 先按照四元组查询hash3。
  • udp_lib_rehash: 如果探测到元组变化,则更新hash3。
  • udp_lib_unhash: 结束时,从hash3中摘除当前socket四元组item。

是不是很简单呢?当然,经理并不这么认为。


浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/104219341

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    UDP socket查询高速缓存_udp_lib_unhash

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/406115

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:37
下一篇 2023年4月26日 上午9:37

相关推荐