dnspr: name servers asked from the loop, nothing waits in it

dnspr answered an A or AAAA query missing from the name cache with
udpresolve(), which waited for the name servers in place, and a name
server over TCP was waited for in place too, holding up every query of
the service. Now only the name cache is looked at in place; anything it
does not have goes to the name servers from the loop: over UDP the query
is sent on a socket the loop waits on, over TCP the socket is connected
without waiting, the query written once the loop sees it writable, and
the answer read in as many pieces as it comes. A name server silent for
DNS_TO is followed by the next one, as udpresolve() did; the last ends
the query with 821. An address in an answer is kept in the name cache,
unless -s says not to use it.

The UDP loop lets a session say what it waits for on its socket
(events) and a service handle a session's deadline itself (timeout).

Tests: an A query not in the cache answered by the name server and then
from the cache with the server gone, an A query waiting for a silent
name server while another is answered, a name server over TCP whose
answer comes in pieces, and the second name server asked when the
first is silent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-18 16:50:34 +03:00
parent f28552fc6f
commit 58334cb51c
5 changed files with 296 additions and 97 deletions

View File

@ -15,22 +15,30 @@
#define BUFSIZE 16384 #define BUFSIZE 16384
/* dnspr: a query is a session of its own in the service's thread (see /* dnspr: a query is a session of its own in the service's thread (see
udploop() in proxymain.c). It is answered there and then, from the name udploop() in mainudp.c). It is answered there and then from a fake
cache, the resolver or a fake address, or sent on to the first name address, the name cache, or the service's own address for a PTR;
server, whose answer, or its timeout, ends it. A name server over TCP is anything else goes to the name servers, one after another, over UDP or
still waited for in the loop. */ TCP, the loop waiting for their sockets: nothing here blocks. An address
in an answer is kept in the name cache. */
struct dnsquery { struct dnsquery {
unsigned type; unsigned type;
uint32_t ip, ttl; uint32_t ip, ttl;
unsigned char addr[16]; unsigned char addr[16];
char *host; char *host;
unsigned char *query; /* as the client sent it, for a name server */ /* the query as the client sent it, behind 2 bytes for its length over
TCP */
unsigned char *query;
int len; int len;
/* the name server asked, and over TCP what of its answer came */
int server;
int tcp, connecting;
unsigned char *answer;
int got;
}; };
/* The answer to the client, the query in buf made one: ip set, an address /* The answer to the client, the query in buf made one: ip set, an address
for it; a PTR for the service's own address; else the name not found. */ for it; else the name not found. */
static int dnsanswer(struct udploop *u, struct clientparam *param, struct dnsquery *q, unsigned char *buf, int len){ static int dnsanswer(struct udploop *u, struct clientparam *param, struct dnsquery *q, unsigned char *buf, int len){
uint32_t ttl; uint32_t ttl;
@ -59,6 +67,113 @@ static int dnsanswer(struct udploop *u, struct clientparam *param, struct dnsque
return q->ip? 0 : 888; return q->ip? 0 : 888;
} }
/* An address of the type asked for in a name server's answer: noted for the
log and kept in the name cache for its TTL, up to 12 hours. */
static void dnscache(struct clientparam *param, struct dnsquery *q, unsigned char *buf, int len){
int k = 12, n, j, rlen;
unsigned type;
uint32_t ttl;
if((q->type != 0x01 && q->type != 0x1c) || !q->host) return;
while(k < len && buf[k]) k += buf[k] + 1;
k += 5;
n = buf[7] + (((unsigned)buf[6])<<8);
for(j = 0; j < n && k < len; j++){
/* the name: a pointer, or labels to the end */
if((buf[k] & 0xc0) == 0xc0) k += 2;
else {
while(k < len && buf[k]) k += buf[k] + 1;
k++;
}
if(k + 10 > len) return;
type = buf[k+1] + (((unsigned)buf[k])<<8);
rlen = buf[k+9] + (((unsigned)buf[k+8])<<8);
if(k + 10 + rlen > len) return;
if(type == q->type && rlen == (q->type == 0x01? 4 : 16)){
ttl = ntohl(*(uint32_t *)(buf + k + 4));
if(ttl > 3600*12) ttl = 3600*12;
if(!ttl) ttl = 1;
memcpy(q->addr, buf + k + 10, rlen);
q->ip = 1;
if(!param->srv->s_option)
hashadd(q->type == 0x1c? &dns6_table : &dns_table, (unsigned char *)q->host, q->addr, curtime + ttl);
return;
}
k += 10 + rlen;
}
}
/* An answer from a name server: to the client as it is if it has answers,
else as the name not found. 1: the query is done. */
static int dnsreply(struct udploop *u, struct udpsession *ss, unsigned char *buf, int len){
struct clientparam *param = ss->param;
struct dnsquery *q = ss->data;
param->statssrv64 += len;
param->nreads++;
if(buf[6] || buf[7]){
dnscache(param, q, buf, len);
param->res = (udpanswer(u, param, buf, len) == len)? 0 : 822;
return 1;
}
q->ip = 0;
param->res = dnsanswer(u, param, q, buf, len);
return 1;
}
/* The query to name server q->server or a later one: 0 once it is on its
way, the loop to wait for the answer; nonzero if none takes it. */
static int dnsforward(struct udploop *u, struct udpsession *ss){
struct clientparam *param = ss->param;
struct dnsquery *q = ss->data;
struct extparam *c = param->srv->config;
#ifdef _WIN32
unsigned long ul = 1;
#endif
for(; q->server < c->numservers; q->server++){
if(param->remsock != INVALID_SOCKET){
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
}
q->tcp = c->nservers[q->server].usetcp;
q->got = 0;
if((param->remsock = param->srv->so._socket(param->sostate, SASOCK(&c->nservers[q->server].addr),
q->tcp? SOCK_STREAM : SOCK_DGRAM, q->tcp? IPPROTO_TCP : IPPROTO_UDP)) == INVALID_SOCKET) continue;
#ifdef _WIN32
ioctlsocket(param->remsock, FIONBIO, &ul);
#else
fcntl(param->remsock, F_SETFL, O_NONBLOCK | fcntl(param->remsock, F_GETFL));
#endif
memset(&param->sinsl, 0, sizeof(param->sinsl));
*SAFAMILY(&param->sinsl) = *SAFAMILY(&c->nservers[q->server].addr);
if(bindwithrange(param, param->remsock, &param->sinsl, param->extport)) continue;
param->sinsr = c->nservers[q->server].addr;
ss->idle = 0;
ss->deadline = time(NULL) + param->srv->timeouts[DNS_TO];
if(q->tcp){
/* connected, then written to, as the loop sees it writable */
if(param->srv->so._connect(param->sostate, param->remsock, (struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr))
#ifdef _WIN32
&& WSAGetLastError() != WSAEWOULDBLOCK
#else
&& errno != EINPROGRESS
#endif
) continue;
q->connecting = 1;
ss->events = POLLOUT;
return 0;
}
ss->events = POLLIN;
if(param->srv->so._sendto(param->sostate, param->remsock, (char *)q->query + 2, q->len, 0,
(struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr)) != q->len) continue;
param->statscli64 += q->len;
param->nwrites++;
return 0;
}
return 1;
}
static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *query, int i){ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *query, int i){
struct clientparam *param = ss->param; struct clientparam *param = ss->param;
struct dnsquery *q; struct dnsquery *q;
@ -66,9 +181,6 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
unsigned char c; unsigned char c;
int res, len; int res, len;
uint32_t ttl, ip = 0; uint32_t ttl, ip = 0;
#ifdef _WIN32
unsigned long ul = 1;
#endif
if(!(q = calloc(1, sizeof(*q))) || !(q->query = malloc(BUFSIZE + 2))){ if(!(q = calloc(1, sizeof(*q))) || !(q->query = malloc(BUFSIZE + 2))){
free(q); free(q);
@ -78,21 +190,23 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
if(i > BUFSIZE) i = BUFSIZE; if(i > BUFSIZE) i = BUFSIZE;
memcpy(q->query + 2, query, i); memcpy(q->query + 2, query, i);
q->len = i; q->len = i;
buf = q->query + 2;
param->sincl = param->srv->intsa;
if(i<=13 || i>1000) return (param->res = 814); if(i<=13 || i>1000) return (param->res = 814);
/* the query as it came is what goes to a name server; buf is worked on */
if(!(buf = malloc(BUFSIZE))) return (param->res = 21);
memcpy(buf, query, i);
buf[BUFSIZE - 1] = 0; buf[BUFSIZE - 1] = 0;
param->sincl = param->srv->intsa;
param->operation = DNSRESOLVE; param->operation = DNSRESOLVE;
if((res = (*param->srv->authfunc)(param))) return (param->res = res); if((res = (*param->srv->authfunc)(param))) { free(buf); return (param->res = res); }
if(buf[4]!=0 || buf[5]!=1) return (param->res = 816); if(buf[4]!=0 || buf[5]!=1) { free(buf); return (param->res = 816); }
for(len = 12; len<i; len+=(c+1)){ for(len = 12; len<i; len+=(c+1)){
c = buf[len]; c = buf[len];
if(!c)break; if(!c)break;
buf[len] = '.'; buf[len] = '.';
} }
if(len > (i-4)) return (param->res = 817); if(len > (i-4)) { free(buf); return (param->res = 817); }
if(!(q->host = strdup((char *)buf+13))) return (param->res = 21); if(!(q->host = strdup((char *)buf+13))) { free(buf); return (param->res = 21); }
for(s2 = buf + 12; (s1 = (unsigned char *)strchr((char *)s2 + 1, '.')); s2 = s1)*s2 = (unsigned char)((s1 - s2) - 1); for(s2 = buf + 12; (s1 = (unsigned char *)strchr((char *)s2 + 1, '.')); s2 = s1)*s2 = (unsigned char)((s1 - s2) - 1);
*s2 = (len - (int)(s2 - buf)) - 1; *s2 = (len - (int)(s2 - buf)) - 1;
@ -108,8 +222,10 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
memcpy(q->addr, param->srv->fakeip6, 16); memcpy(q->addr, param->srv->fakeip6, 16);
} }
#endif #endif
/* the name cache only: what it does not have the name servers are
asked, without waiting here */
else if((q->type==0x01 || q->type==0x1c) && !param->srv->s_option){ else if((q->type==0x01 || q->type==0x1c) && !param->srv->s_option){
ip = udpresolve((q->type==0x1c)?AF_INET6:AF_INET, (unsigned char *)q->host, q->addr, &q->ttl, param, 0); ip = hashresolv(q->type==0x1c? &dns6_table : &dns_table, (unsigned char *)q->host, q->addr, &q->ttl);
} }
len += 5; len += 5;
@ -137,85 +253,73 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
memcpy(buf+len+13,(void *)"3proxy",6); memcpy(buf+len+13,(void *)"3proxy",6);
len += 20; len += 20;
q->ip = 1; q->ip = 1;
if(udpanswer(u, param, buf, len) != len) return (param->res = 819); param->res = (udpanswer(u, param, buf, len) == len)? 0 : 819;
free(buf);
return 1; return 1;
} }
} }
q->ip = ip; q->ip = ip;
if(ip || !param->srv->config->numservers){ if(ip || !param->srv->config->numservers){
param->res = dnsanswer(u, param, q, buf, len); param->res = dnsanswer(u, param, q, buf, len);
free(buf);
return 1; return 1;
} }
free(buf);
/* on to the first name server */ if(dnsforward(u, ss)) return (param->res = 818);
if((param->remsock=param->srv->so._socket(param->sostate, SASOCK(&param->srv->config->nservers[0].addr), param->srv->config->nservers[0].usetcp? SOCK_STREAM:SOCK_DGRAM, param->srv->config->nservers[0].usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET)
return (param->res = 818);
memset(&param->sinsl, 0, sizeof(param->sinsl));
*SAFAMILY(&param->sinsl) = *SAFAMILY(&param->srv->config->nservers[0].addr);
if(bindwithrange(param, param->remsock, &param->sinsl, param->extport)) return (param->res = 819);
param->sinsr = param->srv->config->nservers[0].addr;
if(param->srv->config->nservers[0].usetcp) {
unsigned short us;
if(connectwithpoll(param, param->remsock,(struct sockaddr *)&param->sinsr,SASIZE(&param->sinsr),param->srv->timeouts[CONNECT_TO])) return (param->res = 830);
buf -= 2;
*(unsigned short*)buf = htons(i);
i += 2;
if(socksend(param, param->remsock, buf, i, param->srv->timeouts[SINGLEBYTE_L]) != i) return (param->res = 820);
param->statscli64 += i;
param->nwrites++;
len = sockrecvfrom(param, param->remsock, NULL, buf, BUFSIZE, param->srv->timeouts[DNS_TO]*1000);
if(len <= 13) return (param->res = 821);
param->statssrv64 += len;
param->nreads++;
us = ntohs(*(unsigned short *)buf);
if(us > 4096) return (param->res = 833);
buf += 2;
len -= 2;
if(len < us) len += sockgetlinebuf(param, SERVER, buf+len, us - len, 0, param->srv->timeouts[SINGLEBYTE_L]);
if(len != us) return (param->res = 832);
if(buf[6] || buf[7]){
if(udpanswer(u, param, buf, len) != len) return (param->res = 822);
return 1;
}
param->res = dnsanswer(u, param, q, buf, len);
return 1;
}
#ifdef _WIN32
ioctlsocket(param->remsock, FIONBIO, &ul);
#else
fcntl(param->remsock, F_SETFL, O_NONBLOCK | fcntl(param->remsock, F_GETFL));
#endif
if(param->srv->so._sendto(param->sostate, param->remsock, (char *)buf, i, 0, (struct sockaddr *)&param->sinsr, SASIZE(&param->sinsr)) != i)
return (param->res = 820);
param->statscli64 += i;
param->nwrites++;
/* its answer is waited for in the loop, as long as DNS_TO */
ss->idle = 0;
ss->deadline = time(NULL) + param->srv->timeouts[DNS_TO];
return 0; return 0;
} }
/* The name server's answer: to the client as it is if it has answers, /* The name server's socket: connected, over TCP, so the query is written;
else as the name not found. */ an answer, or over TCP some of it. */
static int dnsprserver(struct udploop *u, struct udpsession *ss){ static int dnsprserver(struct udploop *u, struct udpsession *ss){
struct clientparam *param = ss->param; struct clientparam *param = ss->param;
struct dnsquery *q = ss->data; struct dnsquery *q = ss->data;
int len; int len;
if(q->tcp && q->connecting){
q->connecting = 0;
q->query[0] = (unsigned char)(q->len >> 8);
q->query[1] = (unsigned char)(q->len & 0xff);
if(param->srv->so._send(param->sostate, param->remsock, (char *)q->query, q->len + 2, 0) != q->len + 2){
q->server++;
return dnsforward(u, ss)? (param->res = 820) : 0;
}
param->statscli64 += q->len + 2;
param->nwrites++;
ss->events = POLLIN;
return 0;
}
if(q->tcp){
unsigned short us;
if(!q->answer && !(q->answer = malloc(4096 + 2))) return (param->res = 21);
len = param->srv->so._recv(param->sostate, param->remsock, (char *)q->answer + q->got, 4096 + 2 - q->got, 0);
if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
if(len <= 0){
q->server++;
return dnsforward(u, ss)? (param->res = 821) : 0;
}
q->got += len;
if(q->got < 2) return 0;
us = (unsigned short)((q->answer[0] << 8) | q->answer[1]);
if(us > 4096 || us <= 13) return (param->res = 833);
if(q->got < us + 2) return 0;
return dnsreply(u, ss, q->answer + 2, us);
}
len = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)u->buf, UDPBUFSIZE, 0, NULL, NULL); len = param->srv->so._recvfrom(param->sostate, param->remsock, (char *)u->buf, UDPBUFSIZE, 0, NULL, NULL);
if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0; if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
if(len <= 13) return (param->res = 821); /* an answer to another query, or none at all */
param->statssrv64 += len; if(len <= 13 || u->buf[0] != q->query[2] || u->buf[1] != q->query[3]) return 0;
param->nreads++; return dnsreply(u, ss, u->buf, len);
if(u->buf[6] || u->buf[7]){ }
if(udpanswer(u, param, u->buf, len) != len) return (param->res = 822);
param->res = 0; /* No answer in DNS_TO: the next name server, if there is one. */
return 1; static int dnsprtimeout(struct udploop *u, struct udpsession *ss){
} struct dnsquery *q = ss->data;
q->ip = 0;
param->res = dnsanswer(u, param, q, u->buf, len); q->server++;
return 1; if(dnsforward(u, ss)) return (ss->param->res = 821);
return 0;
} }
static void dnsprend(struct udploop *u, struct udpsession *ss){ static void dnsprend(struct udploop *u, struct udpsession *ss){
@ -233,6 +337,7 @@ static void dnsprend(struct udploop *u, struct udpsession *ss){
if(q){ if(q){
free(q->host); free(q->host);
free(q->query); free(q->query);
free(q->answer);
free(q); free(q);
} }
ss->data = NULL; ss->data = NULL;
@ -244,5 +349,6 @@ const struct udpservice dnsprservice = {
NULL, NULL,
dnsprserver, dnsprserver,
dnsprend, dnsprend,
821 821,
dnsprtimeout
}; };

View File

@ -344,7 +344,7 @@ static int udploop(struct srvstart *s){
timeo = ss->deadline > now? (int)(ss->deadline - now) * 1000 : 0; timeo = ss->deadline > now? (int)(ss->deadline - now) * 1000 : 0;
if(ss->param->remsock != INVALID_SOCKET){ if(ss->param->remsock != INVALID_SOCKET){
st.fds[nfds].fd = ss->param->remsock; st.fds[nfds].fd = ss->param->remsock;
st.fds[nfds].events = POLLIN; st.fds[nfds].events = ss->events? ss->events : POLLIN;
st.fds[nfds].revents = 0; st.fds[nfds].revents = 0;
st.of[nfds++] = ss; st.of[nfds++] = ss;
} }
@ -375,9 +375,12 @@ static int udploop(struct srvstart *s){
for(ss = st.head; ss; ss = next){ for(ss = st.head; ss; ss = next){
next = ss->next; next = ss->next;
if(!ss->done && ss->deadline && ss->deadline <= now){ if(!ss->done && ss->deadline && ss->deadline <= now){
if(st.svc->timeout) ss->done = (*st.svc->timeout)(&st.u, ss);
else {
ss->done = 1; ss->done = 1;
ss->param->res = st.svc->timeoutres; ss->param->res = st.svc->timeoutres;
} }
}
if(ss->done){ if(ss->done){
udpremove(&st, ss); udpremove(&st, ss);
udpend(ss); udpend(ss);

View File

@ -252,6 +252,7 @@ struct udpsession {
int idle; /* seconds of silence it lasts, 0: deadline fixed */ int idle; /* seconds of silence it lasts, 0: deadline fixed */
int keyed; /* found by its client's address */ int keyed; /* found by its client's address */
int done; /* to be ended, param->res saying how */ int done; /* to be ended, param->res saying how */
short events; /* polled for on remsock, 0: POLLIN */
void *data; /* the service's own, freed by it in end() */ void *data; /* the service's own, freed by it in end() */
}; };
struct udpservice { struct udpservice {
@ -269,6 +270,9 @@ struct udpservice {
void (*end)(struct udploop *u, struct udpsession *ss); void (*end)(struct udploop *u, struct udpsession *ss);
/* param->res of a session whose deadline passed */ /* param->res of a session whose deadline passed */
int timeoutres; int timeoutres;
/* or, if set, its deadline passed: 0 if it goes on, a new deadline
set, nonzero to end it, param->res saying how */
int (*timeout)(struct udploop *u, struct udpsession *ss);
}; };
struct udploop { struct udploop {
struct srvparam *srv; struct srvparam *srv;

View File

@ -109,7 +109,8 @@ const struct udpservice udppmservice = {
udppmclient, udppmclient,
udppmserver, udppmserver,
udppmend, udppmend,
92 92,
NULL
}; };
#ifdef WITHMAIN #ifdef WITHMAIN

View File

@ -24,28 +24,64 @@ def dns_raw(name, qtype, qid=0x3B3B):
return q + b"\x00" + struct.pack("!HH", qtype, 1) return q + b"\x00" + struct.pack("!HH", qtype, 1)
def fake_name_server(answer): def dns_reply(data, answer):
"""A name server answering every query with one TXT record, the text """The answer to a query: an A record 10.9.8.7 for an A query, else a TXT
given; and one which answers nothing.""" record with the text given."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
def serve():
while True:
try:
data, peer = sock.recvfrom(65536)
except OSError:
return
if answer is None:
continue
reply = bytearray(data) reply = bytearray(data)
reply[2] = 0x81 reply[2] = 0x81
reply[3] = 0x80 reply[3] = 0x80
reply[6:8] = struct.pack("!H", 1) reply[6:8] = struct.pack("!H", 1)
pos = 12
while data[pos]:
pos += data[pos] + 1
qtype = struct.unpack("!H", data[pos + 1:pos + 3])[0]
if qtype == 1:
reply += b"\xc0\x0c" + struct.pack("!HHIH", 1, 1, 60, 4) + bytes([10, 9, 8, 7])
else:
txt = bytes([len(answer)]) + answer txt = bytes([len(answer)]) + answer
reply += b"\xc0\x0c" + struct.pack("!HHIH", 16, 1, 60, len(txt)) + txt reply += b"\xc0\x0c" + struct.pack("!HHIH", 16, 1, 60, len(txt)) + txt
sock.sendto(bytes(reply), peer) return bytes(reply)
def fake_name_server(answer, tcp=False):
"""A name server answering every query (see dns_reply), over UDP or
TCP; with answer None, one which answers nothing."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM if tcp else socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 0))
def serve_tcp(c):
try:
c.settimeout(10)
head = c.recv(2)
n = struct.unpack("!H", head)[0]
data = b""
while len(data) < n:
data += c.recv(n - len(data))
reply = dns_reply(data, answer)
# in two pieces, as a stream may bring it
c.sendall(struct.pack("!H", len(reply)) + reply[:5])
time.sleep(0.05)
c.sendall(reply[5:])
except OSError:
pass
c.close()
def serve():
while True:
try:
if tcp:
c, _ = sock.accept()
threading.Thread(target=serve_tcp, args=(c,), daemon=True).start()
continue
data, peer = sock.recvfrom(65536)
except OSError:
return
if answer is not None:
sock.sendto(dns_reply(data, answer), peer)
if tcp:
sock.listen(8)
threading.Thread(target=serve, daemon=True).start() threading.Thread(target=serve, daemon=True).start()
return sock, sock.getsockname()[1] return sock, sock.getsockname()[1]
@ -201,9 +237,22 @@ def run(t):
if got and b"from-the-server" in got else \ if got and b"from-the-server" in got else \
t.fail("a TXT query is sent on to the name server and its answer relayed", "the TXT", got) t.fail("a TXT query is sent on to the name server and its answer relayed", "the TXT", got)
# an address not in the cache is asked of the name server, and kept
got = exchange(s, fwd, dns_raw("a.test", 1))
t.ok("an A query not in the cache is answered by the name server") \
if got and bytes([10, 9, 8, 7]) in got else \
t.fail("an A query not in the cache is answered by the name server", "10.9.8.7", got)
good.close()
time.sleep(0.2)
got = exchange(s, fwd, dns_raw("a.test", 1))
t.ok("and then from the name cache, the name server gone") \
if got and bytes([10, 9, 8, 7]) in got else \
t.fail("and then from the name cache, the name server gone", "10.9.8.7", got)
t.wait_udp(slow, payload=dns_raw("local.test", 1)) t.wait_udp(slow, payload=dns_raw("local.test", 1))
pending = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) pending = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
pending.sendto(dns_raw("mute.test", 16), ("127.0.0.1", slow)) # an A query, which the resolver used to wait for in place
pending.sendto(dns_raw("mute.test", 1), ("127.0.0.1", slow))
start = time.time() start = time.time()
other = exchange(s, slow, dns_raw("local.test", 1), timeout=2) other = exchange(s, slow, dns_raw("local.test", 1), timeout=2)
t.ok(f"while one query waits for a silent name server another is answered ({time.time() - start:.2f} s)") \ t.ok(f"while one query waits for a silent name server another is answered ({time.time() - start:.2f} s)") \
@ -213,5 +262,41 @@ def run(t):
while time.time() < deadline and " 00821 " not in dns2.output(): while time.time() < deadline and " 00821 " not in dns2.output():
time.sleep(0.2) time.sleep(0.2)
t.contains(dns2.output(), " 00821 ", "the waiting one ends at DNS_TO (821)") t.contains(dns2.output(), " 00821 ", "the waiting one ends at DNS_TO (821)")
for x in (s, pending, good, mute):
# a name server over TCP; and a second one asked when the first is silent
tcpns, tport = fake_name_server(b"over-tcp", tcp=True)
second, sport = fake_name_server(b"the-second")
viatcp, fallback = t.free_port(), t.free_port()
t.start("udploop_tcpns", f"""
log
timeouts 1 5 30 60 180 1800 2 60 15 5 5
nserver 127.0.0.1:{tport}/tcp
nscache 256
nsrecord local.test 10.1.2.3
auth iponly
allow *
dnspr -p{viatcp}
""")
t.start("udploop_fallback", f"""
log
timeouts 1 5 30 60 180 1800 2 60 15 5 5
nserver 127.0.0.1:{mport}
nserver 127.0.0.1:{sport}
nscache 256
nsrecord local.test 10.1.2.3
auth iponly
allow *
dnspr -p{fallback}
""")
t.wait_udp(viatcp, payload=dns_raw("local.test", 1))
got = exchange(s, viatcp, dns_raw("tcp.test", 16), timeout=4)
t.ok("a name server over TCP is asked, its answer in pieces put together") \
if got and b"over-tcp" in got else \
t.fail("a name server over TCP is asked, its answer in pieces put together", "the TXT", got)
t.wait_udp(fallback, payload=dns_raw("local.test", 1))
got = exchange(s, fallback, dns_raw("second.test", 16), timeout=6)
t.ok("the first name server silent, the second is asked after DNS_TO") \
if got and b"the-second" in got else \
t.fail("the first name server silent, the second is asked after DNS_TO", "the TXT", got)
for x in (s, pending, mute, tcpns, second):
x.close() x.close()