环境: CentOS 7、 X86_64、 nginx version: openresty/1.17.8.1 built by gcc 4.8.5、 stream-lua-nginx-module-0.0.8
用openresty 写了个 DNS server,我们在这个基础上修改了下,变成一个内网的DNS。 域名记录放在了redis里,读取后根据TTL 缓存在共享内存里。
-- test.lua:
function _M.go()
local client_addr = ngx.var.remote_addr
--... 处理DNS请求...
ngx.sleep(0.001); --只要这里暂停 1毫秒,或者去 redis取 数据,都会造成下面的一个问题。
if typ == TYPE_A then
return send_a_ans(id, sock, quest_qname, raw_quest_rr, raw_quest_name, "8.8.8.8")
else
ngx.log(ngx.INFO, "do not support this type: ", typ)
return send_no_rdata(id, sock, raw_quest_rr)
end
end
`/*
模拟系统请求的客户端:
设置 /etc/resolv.conf 的 nameserver 192.168.2.120
192.168.2.120 是openresty搭建的 本地DNS server
*/
// cli.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main (int argc, char *argv[]) {
int s;
struct addrinfo hints, *result, *rp;
char m_ipaddr[16];
struct sockaddr_in *addr;
if (argc != 3) {
fprintf(stderr, "Usage: %s host port\n", argv[0]);
exit( EXIT_FAILURE );
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = 0;
hints.ai_protocol = 0;
s = getaddrinfo(argv[1], argv[2], &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
addr = (struct sockaddr_in *)rp->ai_addr;
printf("ai_addrlen = %d\n", rp->ai_addrlen);
}
freeaddrinfo(result);
return 0;
}
现象:
就是客户端向服务端用同一个 源端口 发送 1个A 记录请求和一个 AAAA 记录请求, 服务端只会响应1个请求,第二个请求会不响应,但是nginx的 event 那里是收取到了报文。
简单的跟踪源码发现是因为 网络五元组相同, 复用了连接的结构体,然后第一个请求响应的时候会把第二个请求的结构体删除了,导致第二个 AAAA的请求没有了响应。
强制的把nginx的 结构体复用那段代码跳过,或者在第二个AAAA请求进来之前就响应完第一个A请求,都不会发生这个现象。 所以在lua代码里 连接redis或者sleep 1毫秒 必定触发。
有什么好的建议吗? 目前项目中是修改了nginx的代码,可以规避这个问题,如下图:
event/ngx_event_udp.c
这个问题其实之前有人提出来,只是没有得到解决。
如下:https://github.com/openresty/stream-lua-nginx-module/issues/210:
我试了最新版本的openresty 1.19,这个问题依旧存在。