3proxy/src/resolve.c
Vladimir Dubrovin 21ba6e9d0c Reload the configuration atomically
A reload reads the file into a new configuration, allocated from the
defaults, and prepares its services without starting them. Only if the
whole file is read without an error does it come into force: the old
configuration's services stop accepting, the new ones start. On an
error everything prepared is dropped and the old configuration, which
never stopped, stays in force.

Each service holds its configuration until it is freed, after its last
client; a configuration no longer in force is released when the last
holder lets go (confhold/confput). freeconf() now only releases what a
configuration holds, touching nothing global.

What runs without a service of its own reads the configuration in
force, runconf, rather than conf, which a reload points at the one
being read: the resolver, which holds it while it waits (a client
resolves with its own service's), the log, the thread pool, the admin
pages, which walk the services under config_mutex, and the signal
handlers.

The users table, havelog and the resolver choice are per configuration
now, the resolver applied when the configuration comes into force, so
a refused file leaves the users as they were. The log file, the
configuration file name, the string table, daemon mode and the filter
list belong to the process and are handed from one configuration to
the next. Settings left out of a new file go back to their defaults,
where before they kept the old values.

The gap between the old and the new listening sockets remains: a
connection queued on an old service's socket when it closes is reset.
Handing the socket over is the next phase.

A test case reloads a broken file (nothing changes, alice still gets
in, bob from the refused file does not) and a good one (bob only, the
service on its new port, the old one closed, a download through the
old service finishing whole). 40 reloads, a third refused, under load
show no memory errors under AddressSanitizer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:15:14 +03:00

248 lines
7.0 KiB
C

#include "proxy.h"
static int dnsqmatch(const unsigned char *a, const unsigned char *b, int len){
int i;
for(i = 0; i < len; i++){
unsigned char c1 = a[i], c2 = b[i];
if(c1 >= 'A' && c1 <= 'Z') c1 += 'a' - 'A';
if(c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A';
if(c1 != c2) return 1;
}
return 0;
}
static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth){
int i,n;
uint32_t retval;
if((af == AF_INET) && (retval = hashresolv(&dns_table, name, value, retttl))) {
return retval;
}
if((af == AF_INET6) && (retval = hashresolv(&dns6_table, name, value, retttl))) {
return retval;
}
n = (makeauth && !SAISNULL(&c->authnserver.addr))? 1 : c->numservers;
for(i=0; i<n; i++){
unsigned short nq, na;
unsigned char b[4098], *buf, *s1, *s2;
int j, k, len, flen;
SOCKET sock;
uint32_t ttl;
PROXYSOCKADDRTYPE addr;
PROXYSOCKADDRTYPE from;
PROXYSOCKADDRTYPE *sinsr, *sinsl;
int usetcp = 0;
unsigned short serial = 1;
unsigned char qbuf[300];
int qlen;
buf = b+2;
sinsl = (param && !makeauth)? &param->sinsl : &addr;
sinsr = (param && !makeauth)? &param->sinsr : &addr;
memset(sinsl, 0, sizeof(addr));
memset(sinsr, 0, sizeof(addr));
if(makeauth && !SAISNULL(&c->authnserver.addr)){
usetcp = c->authnserver.usetcp;
*SAFAMILY(sinsl) = *SAFAMILY(&c->authnserver.addr);
}
else {
usetcp = c->nservers[i].usetcp;
*SAFAMILY(sinsl) = *SAFAMILY(&c->nservers[i].addr);
}
if((sock=so._socket(so.state, SASOCK(sinsl), usetcp?SOCK_STREAM:SOCK_DGRAM, usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) break;
if(so._bind(so.state, sock,(struct sockaddr *)sinsl,SASIZE(sinsl))){
so._shutdown(so.state, sock, SHUT_RDWR);
so._closesocket(so.state, sock);
break;
}
if(makeauth && !SAISNULL(&c->authnserver.addr)){
*sinsr = c->authnserver.addr;
}
else {
*sinsr = c->nservers[i].addr;
}
if(usetcp){
if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),c->timeouts[CONNECT_TO])) {
so._shutdown(so.state, sock, SHUT_RDWR);
so._closesocket(so.state, sock);
break;
}
#ifdef TCP_NODELAY
{
int opt = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&opt, sizeof(opt));
}
#endif
}
len = (int)strlen((char *)name);
serial = myrand();
*(unsigned short*)buf = serial; /* query id */
buf[2] = 1; /* recursive */
buf[3] = 0;
buf[4] = 0;
buf[5] = 1; /* 1 request */
buf[6] = buf[7] = 0; /* no replies */
buf[8] = buf[9] = 0; /* no ns count */
buf[10] = buf[11] = 0; /* no additional */
if(len > 255) {
len = 255;
}
memcpy(buf + 13, name, len);
len += 13;
buf[len] = 0;
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;
len++;
buf[len++] = 0;
buf[len++] = (makeauth == 1)? 0x0c : (af==AF_INET6? 0x1c:0x01);/* PTR:host address */
buf[len++] = 0;
buf[len++] = 1; /* INET */
qlen = len - 12;
memcpy(qbuf, buf + 12, qlen);
if(usetcp){
buf-=2;
*(unsigned short*)buf = htons(len);
len+=2;
}
if(socksendto(NULL, sock, (struct sockaddr *)sinsr, buf, len, c->timeouts[SINGLEBYTE_L]*1000) != len){
so._shutdown(so.state, sock, SHUT_RDWR);
so._closesocket(so.state, sock);
continue;
}
if(param) param->statscli64 += len;
len = sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf, 4096, c->timeouts[DNS_TO]*1000);
if(len > 13 && usetcp){
unsigned short us;
us = ntohs(*(unsigned short*)buf);
len-=2;
buf+=2;
if(us > 4096 || us < len) len = 0;
else if(us > len){
if(sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf+len, us-len, c->timeouts[DNS_TO]*1000) != us-len) len = 0;
else len = us;
}
}
so._shutdown(so.state, sock, SHUT_RDWR);
so._closesocket(so.state, sock);
if(len <= 13) {
continue;
}
if(param) param->statssrv64 += len;
if(!usetcp && (*SAFAMILY(&from) != *SAFAMILY(sinsr) ||
memcmp(SAADDR(&from), SAADDR(sinsr), SAADDRLEN(sinsr)) ||
*SAPORT(&from) != *SAPORT(sinsr))) {
continue; /* not from the server we asked */
}
if(*(unsigned short *)buf != serial)continue;
if(!(buf[2] & 0x80)) continue; /* not a response */
nq = buf[5] + (((unsigned short)buf[4])<<8);
if (nq != 1) {
continue; /* we did only 1 request */
}
if(len < 12 + qlen || dnsqmatch(buf + 12, qbuf, qlen)) {
continue; /* question does not match the query */
}
if((na = buf[7] + (((unsigned short)buf[6])<<8)) < 1) {
return 0;
}
for(k = 13; k<len && buf[k]; k++) {
}
k++;
if( (k+4) >= len) {
continue;
}
k += 4;
if(na > 255) na = 255; /* somebody is very evil */
for (j = 0; j < na; j++) { /* now there should be answers */
while(buf[k] < 192 && buf[k] !=0 && (k+buf[k]+14) < len) k+= (buf[k] + 1);
if(!buf[k]) k--;
if((k+(af == AF_INET6?28:16)) > len) {
break;
}
flen = buf[k+11] + (((unsigned short)buf[k+10])<<8);
if((k+12+flen) > len) {
break;
}
if(makeauth != 1){
if(buf[k+2] != 0 || buf[k+3] != (af == AF_INET6?0x1c:0x1) || flen != (af == AF_INET6?16:4)) {
k+= (12 + flen);
continue; /* we need A IPv4 */
}
ttl = ntohl(*(uint32_t *)(buf + k + 6));
memcpy(value, buf + k + 12, af == AF_INET6? 16:4);
if(ttl < 0 || ttl > (3600*12)) ttl = 3600*12;
if(!ttl) ttl = 1;
hashadd(af == AF_INET6?&dns6_table:&dns_table, name, value, curtime+ttl);
if(retttl) *retttl = ttl;
return 1;
}
else {
if(buf[k+2] != 0 || buf[k+3] != 0x0c) {
k+= (12 + flen);
continue; /* we need A PTR */
}
for (s2 = buf + k + 12; s2 < (buf + k + 12 + len) && *s2; ){
s1 = s2 + ((unsigned)*s2) + 1;
*s2 = '.';
s2 = s1;
}
*s2 = 0;
/* a name longer than the field is no name to authenticate */
if(PARAMSTR(param, username, buf + k + 13)) return 0;
return udpresolvec(c, af,param->username, value, NULL, NULL, 2);
}
}
}
return 0;
}
/* A client resolves with its service's name servers, which its service
holds; anything else with those in force, held while it waits. */
uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth){
struct extparam *c;
uint32_t res;
if(param && param->srv && param->srv->config)
return udpresolvec(param->srv->config, af, name, value, retttl, param, makeauth);
#ifdef STDMAIN
c = conf;
return udpresolvec(c, af, name, value, retttl, param, makeauth);
#else
c = confget();
res = udpresolvec(c, af, name, value, retttl, param, makeauth);
confput(c);
return res;
#endif
}
uint32_t myresolver(int af, unsigned char * name, unsigned char * value){
return udpresolve(af, name, value, NULL, NULL, 0);
}
uint32_t fakeresolver (int af, unsigned char *name, unsigned char * value){
memset(value, 0, af == AF_INET6? 16 : 4);
if(af == AF_INET6){
memset(value, 0, 16);
value[15] = 2;
}
else {
value[0] = 127;
value[1] = 0;
value[2] = 0;
value[3] = 2;
}
return 1;
}