mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-28 22:05:48 +08:00
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>
355 lines
11 KiB
C
355 lines
11 KiB
C
/*
|
|
3APA3A simplest proxy server
|
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
|
|
|
please read License Agreement
|
|
|
|
*/
|
|
|
|
#include "proxy.h"
|
|
|
|
#ifndef UDP
|
|
#define UDP
|
|
#endif
|
|
|
|
#define BUFSIZE 16384
|
|
|
|
/* dnspr: a query is a session of its own in the service's thread (see
|
|
udploop() in mainudp.c). It is answered there and then from a fake
|
|
address, the name cache, or the service's own address for a PTR;
|
|
anything else goes to the name servers, one after another, over UDP or
|
|
TCP, the loop waiting for their sockets: nothing here blocks. An address
|
|
in an answer is kept in the name cache. */
|
|
|
|
struct dnsquery {
|
|
unsigned type;
|
|
uint32_t ip, ttl;
|
|
unsigned char addr[16];
|
|
char *host;
|
|
/* the query as the client sent it, behind 2 bytes for its length over
|
|
TCP */
|
|
unsigned char *query;
|
|
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
|
|
for it; else the name not found. */
|
|
static int dnsanswer(struct udploop *u, struct clientparam *param, struct dnsquery *q, unsigned char *buf, int len){
|
|
uint32_t ttl;
|
|
|
|
if(q->ip){
|
|
buf[2] = 0x85;
|
|
buf[3] = 0x80;
|
|
buf[6] = 0;
|
|
buf[7] = 1;
|
|
buf[8] = buf[9] = buf[10] = buf[11] = 0;
|
|
memset(buf+len, 0, 16);
|
|
buf[len] = 0xc0;
|
|
buf[len+1] = 0x0c;
|
|
buf[len+3] = q->type;
|
|
buf[len+5] = 1;
|
|
ttl = htonl(q->ttl);
|
|
memcpy(buf + len + 6, &ttl, 4);
|
|
buf[len+11] = q->type==1? 4:16;
|
|
memcpy(buf+len+12, q->addr, q->type==1? 4:16);
|
|
len += (q->type==1? 16:28);
|
|
}
|
|
else {
|
|
buf[2] = 0x85;
|
|
buf[3] = 0x83;
|
|
}
|
|
if(udpanswer(u, param, buf, len) != len) return 819;
|
|
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(¶m->sinsl, 0, sizeof(param->sinsl));
|
|
*SAFAMILY(¶m->sinsl) = *SAFAMILY(&c->nservers[q->server].addr);
|
|
if(bindwithrange(param, param->remsock, ¶m->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 *)¶m->sinsr, SASIZE(¶m->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 *)¶m->sinsr, SASIZE(¶m->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){
|
|
struct clientparam *param = ss->param;
|
|
struct dnsquery *q;
|
|
unsigned char *buf, *s1, *s2;
|
|
unsigned char c;
|
|
int res, len;
|
|
uint32_t ttl, ip = 0;
|
|
|
|
if(!(q = calloc(1, sizeof(*q))) || !(q->query = malloc(BUFSIZE + 2))){
|
|
free(q);
|
|
return (param->res = 21);
|
|
}
|
|
ss->data = q;
|
|
if(i > BUFSIZE) i = BUFSIZE;
|
|
memcpy(q->query + 2, query, i);
|
|
q->len = i;
|
|
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;
|
|
param->sincl = param->srv->intsa;
|
|
param->operation = DNSRESOLVE;
|
|
if((res = (*param->srv->authfunc)(param))) { free(buf); return (param->res = res); }
|
|
|
|
if(buf[4]!=0 || buf[5]!=1) { free(buf); return (param->res = 816); }
|
|
for(len = 12; len<i; len+=(c+1)){
|
|
c = buf[len];
|
|
if(!c)break;
|
|
buf[len] = '.';
|
|
}
|
|
if(len > (i-4)) { free(buf); return (param->res = 817); }
|
|
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);
|
|
*s2 = (len - (int)(s2 - buf)) - 1;
|
|
|
|
q->type = ((unsigned)buf[len+1])*256 + (unsigned)buf[len+2];
|
|
q->ttl = 3600;
|
|
if(q->type==0x01 && param->srv->fakeip){
|
|
ip = 1;
|
|
*(uint32_t *)q->addr = param->srv->fakeip;
|
|
}
|
|
#ifndef NOIPV6
|
|
else if(q->type==0x1c && *(uint32_t *)param->srv->fakeip6){
|
|
ip = 1;
|
|
memcpy(q->addr, param->srv->fakeip6, 16);
|
|
}
|
|
#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){
|
|
ip = hashresolv(q->type==0x1c? &dns6_table : &dns_table, (unsigned char *)q->host, q->addr, &q->ttl);
|
|
}
|
|
len += 5;
|
|
|
|
if(!ip && q->type == 0x0c) {
|
|
unsigned a = 0, b = 0, cc = 0, d = 0;
|
|
uint32_t ptr;
|
|
|
|
if(sscanf(q->host, "%u.%u.%u.%u", &a, &b, &cc, &d) != 4) a = b = cc = d = 0;
|
|
ptr = htonl((d<<24) ^ (cc<<16) ^ (b<<8) ^ a);
|
|
if(*SAFAMILY(¶m->sincl) == AF_INET && ptr == *(uint32_t *)SAADDR(¶m->sincl)){
|
|
buf[2] = 0x85;
|
|
buf[3] = 0x80;
|
|
buf[6] = 0;
|
|
buf[7] = 1;
|
|
buf[8] = buf[9] = buf[10] = buf[11] = 0;
|
|
memset(buf+len, 0, 20);
|
|
buf[len] = 0xc0;
|
|
buf[len+1] = 0x0c;
|
|
buf[len+3] = 0x0c;
|
|
buf[len+5] = 1;
|
|
ttl = htonl(3600);
|
|
memcpy(buf + len + 6, &ttl, 4);
|
|
buf[len+11] = 7;
|
|
buf[len+12] = 6;
|
|
memcpy(buf+len+13,(void *)"3proxy",6);
|
|
len += 20;
|
|
q->ip = 1;
|
|
param->res = (udpanswer(u, param, buf, len) == len)? 0 : 819;
|
|
free(buf);
|
|
return 1;
|
|
}
|
|
}
|
|
q->ip = ip;
|
|
if(ip || !param->srv->config->numservers){
|
|
param->res = dnsanswer(u, param, q, buf, len);
|
|
free(buf);
|
|
return 1;
|
|
}
|
|
free(buf);
|
|
if(dnsforward(u, ss)) return (param->res = 818);
|
|
return 0;
|
|
}
|
|
|
|
/* The name server's socket: connected, over TCP, so the query is written;
|
|
an answer, or over TCP some of it. */
|
|
static int dnsprserver(struct udploop *u, struct udpsession *ss){
|
|
struct clientparam *param = ss->param;
|
|
struct dnsquery *q = ss->data;
|
|
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);
|
|
if(len < 0 && (errno == EAGAIN || errno == EINTR)) return 0;
|
|
/* an answer to another query, or none at all */
|
|
if(len <= 13 || u->buf[0] != q->query[2] || u->buf[1] != q->query[3]) return 0;
|
|
return dnsreply(u, ss, u->buf, len);
|
|
}
|
|
|
|
/* No answer in DNS_TO: the next name server, if there is one. */
|
|
static int dnsprtimeout(struct udploop *u, struct udpsession *ss){
|
|
struct dnsquery *q = ss->data;
|
|
|
|
q->server++;
|
|
if(dnsforward(u, ss)) return (ss->param->res = 821);
|
|
return 0;
|
|
}
|
|
|
|
static void dnsprend(struct udploop *u, struct udpsession *ss){
|
|
struct clientparam *param = ss->param;
|
|
struct dnsquery *q = ss->data;
|
|
char line[256];
|
|
|
|
if(param->res != 813){
|
|
sprintf(line, "%04x/%.128s/", q? q->type : 0, q && q->host? q->host : "");
|
|
if(q && ((q->ip && q->type == 0x01) || q->type == 0x1c)){
|
|
myinet_ntop(q->type == 0x01? AF_INET:AF_INET6, q->addr, line+strlen(line), 64);
|
|
}
|
|
dolog(param, (unsigned char *)line);
|
|
}
|
|
if(q){
|
|
free(q->host);
|
|
free(q->query);
|
|
free(q->answer);
|
|
free(q);
|
|
}
|
|
ss->data = NULL;
|
|
}
|
|
|
|
const struct udpservice dnsprservice = {
|
|
0,
|
|
dnsprfirst,
|
|
NULL,
|
|
dnsprserver,
|
|
dnsprend,
|
|
821,
|
|
dnsprtimeout
|
|
};
|