resolver: the system's name servers and hosts file by default

Without nserver (or fakeresolve) names were resolved by the C library.
3proxy's own resolver is now the default, with the system's name
servers: up to 3 from /etc/resolv.conf, on Windows from the registry
(set for the system, set for an interface with an address, Tcpip6
interfaces, then those DHCP gave). With none found, the C library as
before. nserver takes up to 3 as well (MAXNSERVERS), a fourth is
warned about.

The name servers in force belong to the process, not a configuration:
nserver lines are kept aside while a configuration is read and replace
them only once it is committed, so a reload resolves with those in
force until then. Resolutions and dnspr read a copy of them.

The system's are read at start, before the configuration and a chroot
in it, and are in force while it is read, so its host names resolve
with them; read again at a commit, and checked every 5 seconds from
the main loop while the default is in force. What cannot be read then
(within a chroot) stays as last read.

The hosts file, read with them, is imported into tables of its own
while the default is in force: its first address of a family for a
name. A name is looked up as one nsrecord made first, then the hosts
file, then the name cache, then name servers. A changed file replaces
what was imported. No name cache is made for it: without nscache
nothing is cached, as before.

The name cache's keys are host names of any case now: DNS names are.

Tests: sysdns, a name of the hosts file answered by dnspr without
nserver, in any case, and not with one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-20 18:49:21 +03:00
parent 8517a5675b
commit d5e78a0900
11 changed files with 589 additions and 45 deletions

View File

@ -500,14 +500,28 @@ experimental.</p>
<p style="margin-left:9%; margin-top: 1em"><b>nserver</b> <p style="margin-left:9%; margin-top: 1em"><b>nserver</b>
<i>&lt;ipaddr&gt;</i>[:<i>port</i>][/<i>tcp</i>] <br> <i>&lt;ipaddr&gt;</i>[:<i>port</i>][/<i>tcp</i>] <br>
Nameserver to use for name resolutions. If none specified Nameserver to use for name resolutions; up to 3, asked in
system routines for name resolution is used. Optional port order. Optional port number may be specified. If optional /tcp is added to IP
number may be specified. If optional /tcp is added to IP
address, name resolution is performed over TCP. An IPv6 address address, name resolution is performed over TCP. An IPv6 address
has to be enclosed in square brackets: <br> has to be enclosed in square brackets: <br>
<b>nserver 1.1.1.1</b> <br> <b>nserver 1.1.1.1</b> <br>
<b>nserver [2001:4860:4860::8844]</b> <br> <b>nserver [2001:4860:4860::8844]</b> <br>
<b>nserver [2001:4860:4860::8844]:5353/tcp</b></p> <b>nserver [2001:4860:4860::8844]:5353/tcp</b> <br>
If none is specified (and there is no <b>fakeresolve</b>),
the system's name servers are used: up to 3 from
<i>/etc/resolv.conf</i>, on Windows from the registry (those
set for the system, those set for an interface, then those
DHCP gave). Names in the hosts file (<i>/etc/hosts</i>, on
Windows <i>%SystemRoot%\System32\drivers\etc\hosts</i>)
are answered from it, before them, and names in the
configuration are resolved this way too. Both are read
before the configuration, so before a <b>chroot</b>, and
checked for changes every 5 seconds; what cannot be read
later (within a <b>chroot</b>) stays as last read. If no
name server is found, the system routines for name
resolution are used. The <b>nserver</b> lines of a
configuration take effect when it is loaded: during a
reload the name servers in force are used.</p>
<p style="margin-left:9%; margin-top: 1em"><b>authnserver</b> <p style="margin-left:9%; margin-top: 1em"><b>authnserver</b>
@ -533,9 +547,11 @@ rather than per-service.</p>
Adds static record to nscache. <b>nscache</b> must be Adds static record to nscache. <b>nscache</b> must be
enabled and must come first, because the record is placed in enabled and must come first, because the record is placed in
the table it allocates - <b>nscache6</b> for a record naming the table it allocates - <b>nscache6</b> for a record naming
an IPv6 address - and <b>nserver</b> must be set as well: an IPv6 address - and names must be resolved by name servers
without it the system resolver is used and static records (<b>nserver</b>, or the system's ones by default): with the
are never consulted. If 0.0.0.0 is used as a hostaddr host system routines for name resolution static records are never
consulted. A static record takes precedence over the hosts
file. If 0.0.0.0 is used as a hostaddr host
will never resolve, it can be used to blacklist something or will never resolve, it can be used to blacklist something or
together with <b>dialer</b> command to set up UDL for together with <b>dialer</b> command to set up UDL for
dialing.</p> dialing.</p>

View File

@ -543,9 +543,8 @@ Use \fBauthcache\fR to speedup authentication. RADIUS feature is currently exper
.BR nserver .BR nserver
\fI<ipaddr>\fR[:\fIport\fR][/\fItcp\fR] \fI<ipaddr>\fR[:\fIport\fR][/\fItcp\fR]
.br .br
Nameserver to use for name resolutions. If none specified Nameserver to use for name resolutions; up to 3, asked in order.
system routines for name resolution is Optional port number may be specified.
used. Optional port number may be specified.
If optional /tcp is added to IP address, name resolution is If optional /tcp is added to IP address, name resolution is
performed over TCP. performed over TCP.
An IPv6 address has to be enclosed in square brackets: An IPv6 address has to be enclosed in square brackets:
@ -555,6 +554,18 @@ An IPv6 address has to be enclosed in square brackets:
\fBnserver [2001:4860:4860::8844]\fR \fBnserver [2001:4860:4860::8844]\fR
.br .br
\fBnserver [2001:4860:4860::8844]:5353/tcp\fR \fBnserver [2001:4860:4860::8844]:5353/tcp\fR
.br
If none is specified (and there is no \fBfakeresolve\fR), the system's name
servers are used: up to 3 from \fI/etc/resolv.conf\fR, on Windows from the
registry (those set for the system, those set for an interface, then those
DHCP gave). Names in the hosts file (\fI/etc/hosts\fR, on Windows
\fI%SystemRoot%\\System32\\drivers\\etc\\hosts\fR) are answered from it,
before them, and names in the configuration are resolved this way too. Both
are read before the configuration, so before a \fBchroot\fR, and checked for
changes every 5 seconds; what cannot be read later (within a \fBchroot\fR)
stays as last read. If no name server is found, the system routines for name
resolution are used. The \fBnserver\fR lines of a configuration take effect
when it is loaded: during a reload the name servers in force are used.
.br .br
.BR authnserver .BR authnserver
@ -583,8 +594,9 @@ caches are global rather than per-service.
Adds static record to nscache. \fBnscache\fR must be enabled and must come Adds static record to nscache. \fBnscache\fR must be enabled and must come
first, because the record is placed in the table it allocates - \fBnscache6\fR first, because the record is placed in the table it allocates - \fBnscache6\fR
for a record naming an IPv6 address - and for a record naming an IPv6 address - and
\fBnserver\fR must be set as well: without it the system resolver is used and names must be resolved by name servers (\fBnserver\fR, or the system's
static records are never consulted. If 0.0.0.0 ones by default): with the system routines for name resolution static records
are never consulted. A static record takes precedence over the hosts file. If 0.0.0.0
is used as a hostaddr host will never resolve, it can be used to is used as a hostaddr host will never resolve, it can be used to
blacklist something. blacklist something.

View File

@ -293,6 +293,7 @@ void cyclestep(void){
needreload = 0; needreload = 0;
} }
doschedule(); doschedule();
sysdnscheck();
if(conf->stdlog)fflush(conf->stdlog); if(conf->stdlog)fflush(conf->stdlog);
if(timechanged(minutecounter, curtime, MINUTELY)) { if(timechanged(minutecounter, curtime, MINUTELY)) {
struct filemon *fm; struct filemon *fm;
@ -555,6 +556,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
#ifdef __linux__ #ifdef __linux__
nsopen(); /* before a chroot in the configuration takes /proc away */ nsopen(); /* before a chroot in the configuration takes /proc away */
#endif #endif
sysdnsinit(); /* the system's name servers and hosts, before a chroot too */
_3proxy_mutex_init(&config_mutex); _3proxy_mutex_init(&config_mutex);
_3proxy_mutex_init(&confref_mutex); _3proxy_mutex_init(&confref_mutex);
_3proxy_mutex_init(&listener_mutex); _3proxy_mutex_init(&listener_mutex);
@ -592,6 +594,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
/* numbered before it is read, as a reload numbers the new one */ /* numbered before it is read, as a reload numbers the new one */
conf->version++; conf->version++;
res = readconfig(fp); res = readconfig(fp);
if(!res) confdnscommit(conf);
resolvfunc = conf->resolvfunc; resolvfunc = conf->resolvfunc;
/* readconfig() names the failing line, which an exit status would cut /* readconfig() names the failing line, which an exit status would cut

View File

@ -251,6 +251,8 @@ static int startservice(void *service){
return 0; return 0;
} }
static int noresolver(void);
static int h_proxy(int argc, unsigned char ** argv){ static int h_proxy(int argc, unsigned char ** argv){
struct child ch; struct child ch;
@ -267,7 +269,7 @@ static int h_proxy(int argc, unsigned char ** argv){
childdef.helpmessage = " -Xftp - fetch ftp:// URLs\n"; childdef.helpmessage = " -Xftp - fetch ftp:// URLs\n";
#endif #endif
#ifdef NOIPV6 #ifdef NOIPV6
if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){ if(noresolver()){
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, proxy may run very slow\n", linenum); fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, proxy may run very slow\n", linenum);
} }
#endif #endif
@ -306,7 +308,7 @@ static int h_proxy(int argc, unsigned char ** argv){
childdef.isudp = 0; childdef.isudp = 0;
childdef.service = S_SOCKS; childdef.service = S_SOCKS;
#ifdef NOIPV6 #ifdef NOIPV6
if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){ if(noresolver()){
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, socks may run very slow\n", linenum); fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, socks may run very slow\n", linenum);
} }
#endif #endif
@ -374,7 +376,7 @@ static int h_proxy(int argc, unsigned char ** argv){
childdef.service = S_DNSPR; childdef.service = S_DNSPR;
childdef.helpmessage = " -s - simple DNS forwarding - do not use 3proxy resolver / name cache\n -Fip - fake: answer all A queries with this IP\n"; childdef.helpmessage = " -s - simple DNS forwarding - do not use 3proxy resolver / name cache\n -Fip - fake: answer all A queries with this IP\n";
#ifndef NOIPV6 #ifndef NOIPV6
if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize) || conf->resolvfunc == fakeresolver){ if(noresolver() || conf->resolvfunc == fakeresolver){
fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, dnspr will not work as expected\n", linenum); fprintf(stderr, "[line %d] Warning: no nserver/nscache configured, dnspr will not work as expected\n", linenum);
} }
#endif #endif
@ -735,25 +737,43 @@ static int h_flushusers(int argc, unsigned char **argv){
} }
*/ */
/* The name servers of the configuration being read: they replace those in
force (the process has one list) once it is committed */
static struct nserver cfgns[MAXNSERVERS];
static int cfgnsn = 0;
static int h_nserver(int argc, unsigned char **argv){ static int h_nserver(int argc, unsigned char **argv){
char *str; char *str;
if(conf->numservers < MAXNSERVERS) { if(cfgnsn < MAXNSERVERS) {
memset(cfgns + cfgnsn, 0, sizeof(*cfgns));
if((str = strchr((char *)argv[1], '/'))) if((str = strchr((char *)argv[1], '/')))
*str = 0; *str = 0;
*SAPORT(&conf->nservers[conf->numservers].addr) = htons(53); *SAPORT(&cfgns[cfgnsn].addr) = htons(53);
if(parsehost(46, argv[1], (struct sockaddr *)&conf->nservers[conf->numservers].addr)) return 1; if(parsehost(46, argv[1], (struct sockaddr *)&cfgns[cfgnsn].addr)) return 1;
if(str) { if(str) {
conf->nservers[conf->numservers].usetcp = strstr(str + 1, "tcp")? 1:0; cfgns[cfgnsn].usetcp = strstr(str + 1, "tcp")? 1:0;
*str = '/'; *str = '/';
} }
conf->numservers++; cfgnsn++;
} }
else fprintf(stderr, "[line %d] Warning: only %d name servers are used, nserver ignored\n", linenum, MAXNSERVERS);
conf->resolvfunc = myresolver; conf->resolvfunc = myresolver;
return 0; return 0;
} }
/* The configuration's name servers, or the system's in their place */
void confdnscommit(struct extparam *c){
sysdnsapply(c, cfgns, cfgnsn);
}
/* Whether names would be resolved slowly: by the C library, or by name
servers, the system's taken by default, without a cache */
static int noresolver(void){
if(!conf->resolvfunc && !sysdnscount()) return 1;
return (!conf->resolvfunc || conf->resolvfunc == myresolver) && !dns_table.poolsize;
}
static int h_authnserver(int argc, unsigned char **argv){ static int h_authnserver(int argc, unsigned char **argv){
char *str; char *str;
@ -2513,6 +2533,8 @@ void freeconf(struct extparam *confp){
void confbegin(void){ void confbegin(void){
char * args[] = {"auth", "iponly", NULL}; char * args[] = {"auth", "iponly", NULL};
cfgnsn = 0;
h_auth(2, (unsigned char **)args); h_auth(2, (unsigned char **)args);
} }
@ -2643,6 +2665,7 @@ int reload (void){
_3proxy_mutex_lock(&confref_mutex); _3proxy_mutex_lock(&confref_mutex);
runconf = new; runconf = new;
_3proxy_mutex_unlock(&confref_mutex); _3proxy_mutex_unlock(&confref_mutex);
confdnscommit(new);
resolvfunc = new->resolvfunc; resolvfunc = new->resolvfunc;
/* the new services take over the listening sockets of the old ones on /* the new services take over the listening sockets of the old ones on
the same addresses, which then stop accepting, and start */ the same addresses, which then stop accepting, and start */

View File

@ -146,19 +146,20 @@ static int dnsreply(struct udploop *u, struct udpsession *ss, unsigned char *buf
static int dnsforward(struct udploop *u, struct udpsession *ss){ static int dnsforward(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;
struct extparam *c = param->srv->config; struct nserver ns[MAXNSERVERS];
int n = getnservers(ns);
#ifdef _WIN32 #ifdef _WIN32
unsigned long ul = 1; unsigned long ul = 1;
#endif #endif
for(; q->server < c->numservers; q->server++){ for(; q->server < n; q->server++){
if(param->remsock != INVALID_SOCKET){ if(param->remsock != INVALID_SOCKET){
param->srv->so._closesocket(param->sostate, param->remsock); param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET; param->remsock = INVALID_SOCKET;
} }
/* another socket for the loop to watch */ /* another socket for the loop to watch */
ss->regsock = INVALID_SOCKET; ss->regsock = INVALID_SOCKET;
q->tcp = c->nservers[q->server].usetcp; q->tcp = ns[q->server].usetcp;
q->got = 0; q->got = 0;
{ {
unsigned short id = (unsigned short)myrand(); unsigned short id = (unsigned short)myrand();
@ -166,7 +167,7 @@ static int dnsforward(struct udploop *u, struct udpsession *ss){
q->query[2] = (unsigned char)(id >> 8); q->query[2] = (unsigned char)(id >> 8);
q->query[3] = (unsigned char)(id & 0xff); q->query[3] = (unsigned char)(id & 0xff);
} }
if((param->remsock = param->srv->so._socket(param->sostate, SASOCK(&c->nservers[q->server].addr), if((param->remsock = param->srv->so._socket(param->sostate, SASOCK(&ns[q->server].addr),
q->tcp? SOCK_STREAM : SOCK_DGRAM, q->tcp? IPPROTO_TCP : IPPROTO_UDP)) == INVALID_SOCKET) continue; q->tcp? SOCK_STREAM : SOCK_DGRAM, q->tcp? IPPROTO_TCP : IPPROTO_UDP)) == INVALID_SOCKET) continue;
#ifdef _WIN32 #ifdef _WIN32
ioctlsocket(param->remsock, FIONBIO, &ul); ioctlsocket(param->remsock, FIONBIO, &ul);
@ -174,9 +175,9 @@ static int dnsforward(struct udploop *u, struct udpsession *ss){
fcntl(param->remsock, F_SETFL, O_NONBLOCK | fcntl(param->remsock, F_GETFL)); fcntl(param->remsock, F_SETFL, O_NONBLOCK | fcntl(param->remsock, F_GETFL));
#endif #endif
memset(&param->sinsl, 0, sizeof(param->sinsl)); memset(&param->sinsl, 0, sizeof(param->sinsl));
*SAFAMILY(&param->sinsl) = *SAFAMILY(&c->nservers[q->server].addr); *SAFAMILY(&param->sinsl) = *SAFAMILY(&ns[q->server].addr);
if(bindwithrange(param, param->remsock, &param->sinsl, param->extport)) continue; if(bindwithrange(param, param->remsock, &param->sinsl, param->extport)) continue;
param->sinsr = c->nservers[q->server].addr; param->sinsr = ns[q->server].addr;
ss->idle = 0; ss->idle = 0;
ss->deadline = time(NULL) + param->srv->timeouts[DNS_TO]; ss->deadline = time(NULL) + param->srv->timeouts[DNS_TO];
if(q->tcp){ if(q->tcp){
@ -261,7 +262,7 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
/* the name cache only: what it does not have the name servers are /* the name cache only: what it does not have the name servers are
asked, without waiting here */ 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 = hashresolv(q->type==0x1c? &dns6_table : &dns_table, (unsigned char *)q->host, q->addr, &q->ttl); ip = nscacheresolv(q->type==0x1c? AF_INET6 : AF_INET, (unsigned char *)q->host, q->addr, &q->ttl);
} }
len += 5; len += 5;
@ -295,7 +296,7 @@ static int dnsprfirst(struct udploop *u, struct udpsession *ss, unsigned char *q
} }
} }
q->ip = ip; q->ip = ip;
if(ip || !param->srv->config->numservers){ if(ip || !numservers){
param->res = dnsanswer(u, param, q, buf, len); param->res = dnsanswer(u, param, q, buf, len);
free(buf); free(buf);
return 1; return 1;

View File

@ -19,6 +19,19 @@ static void char_index2hash(const struct hashtable *ht, void *index, uint8_t *ha
} }
} }
/* A host name, whose case does not matter: hashed as lower case */
static void dns_index2hash(const struct hashtable *ht, void *index, uint8_t *hash){
char low[1024];
size_t i, len = strlen((const char *)index);
if(len >= sizeof(low)) {
char_index2hash(ht, index, hash);
return;
}
for(i = 0; i <= len; i++) low[i] = (char)tolower(((unsigned char *)index)[i]);
char_index2hash(ht, low, hash);
}
static void param2hash_add(const struct hashtable *ht, void *index, uint8_t *hash){ static void param2hash_add(const struct hashtable *ht, void *index, uint8_t *hash){
struct clientparam *param = (struct clientparam *)index; struct clientparam *param = (struct clientparam *)index;
@ -76,8 +89,11 @@ void param2hash_search(const struct hashtable *ht, void *index, uint8_t *hash){
memcpy(param->hash, hash, ht->hash_size); memcpy(param->hash, hash, ht->hash_size);
} }
struct hashtable dns_table = {char_index2hash, char_index2hash, 4, 32}; struct hashtable dns_table = {dns_index2hash, dns_index2hash, 4, 32};
struct hashtable dns6_table = {char_index2hash, char_index2hash, 16, 32}; struct hashtable dns6_table = {dns_index2hash, dns_index2hash, 16, 32};
/* the hosts file, while the system's resolver is the default */
struct hashtable hosts_table = {dns_index2hash, dns_index2hash, 4, 32};
struct hashtable hosts6_table = {dns_index2hash, dns_index2hash, 16, 32};
struct hashtable auth_table = {param2hash_add, param2hash_search, sizeof(struct authcache), 64}; struct hashtable auth_table = {param2hash_add, param2hash_search, sizeof(struct authcache), 64};
/* users: each configuration has a table of its own, made from this one */ /* users: each configuration has a table of its own, made from this one */
const struct hashtable pwl_template = {char_index2hash, char_index2hash, 64, 64}; const struct hashtable pwl_template = {char_index2hash, char_index2hash, 64, 64};

View File

@ -45,7 +45,7 @@ struct symbol symbols[] = {
{symbols+18, "ipauth", (void *) ipauth}, {symbols+18, "ipauth", (void *) ipauth},
{symbols+19, "strongauth", (void *) strongauth}, {symbols+19, "strongauth", (void *) strongauth},
{symbols+20, "checkACL", (void *) checkACL}, {symbols+20, "checkACL", (void *) checkACL},
{symbols+21, "nservers", (void *) confboot.nservers}, {symbols+21, "nservers", (void *) nservers},
{symbols+22, "udpresolve", (void *) udpresolve}, {symbols+22, "udpresolve", (void *) udpresolve},
{symbols+23, "bandlim_mutex", (void *) &bandlim_mutex}, {symbols+23, "bandlim_mutex", (void *) &bandlim_mutex},
{symbols+24, "tc_mutex", (void *) &tc_mutex}, {symbols+24, "tc_mutex", (void *) &tc_mutex},
@ -87,7 +87,7 @@ static void * findbyname(const char *name){
struct pluginlink pluginlink = { struct pluginlink pluginlink = {
{symbols, "", NULL}, {symbols, "", NULL},
&confboot, &confboot,
confboot.nservers, nservers,
&linenum, &linenum,
authfuncs, authfuncs,
commandhandlers, commandhandlers,

View File

@ -428,6 +428,16 @@ int parseconnusername(char *username, struct clientparam *param, int extpasswd,
int ACLmatches(struct ace* acentry, struct clientparam * param); int ACLmatches(struct ace* acentry, struct clientparam * param);
int checkACL(struct clientparam * param); int checkACL(struct clientparam * param);
char * aceaction (int action); char * aceaction (int action);
extern struct nserver nservers[MAXNSERVERS];
extern int numservers;
int getnservers(struct nserver *ns);
int nscacheresolv(int af, unsigned char *name, unsigned char *value, uint32_t *ttl);
void setnservers(const struct nserver *ns, int n);
void sysdnsinit(void);
int sysdnscount(void);
void sysdnsapply(struct extparam *c, const struct nserver *ns, int n);
void sysdnscheck(void);
void confdnscommit(struct extparam *c);
uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth); uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth);
struct ace * copyacl (struct ace *ac); struct ace * copyacl (struct ace *ac);

View File

@ -1,5 +1,30 @@
#include "proxy.h" #include "proxy.h"
/* The name servers in force, for the process and not a configuration:
those of nserver lines, or else the system's. Read as a copy, they may
change under a resolution. */
struct nserver nservers[MAXNSERVERS];
int numservers = 0;
static _3proxy_mutex_t nservers_mutex;
int getnservers(struct nserver *ns){
int n;
_3proxy_mutex_lock(&nservers_mutex);
n = numservers;
if(ns) memcpy(ns, nservers, n * sizeof(*ns));
_3proxy_mutex_unlock(&nservers_mutex);
return n;
}
void setnservers(const struct nserver *ns, int n){
if(n > MAXNSERVERS) n = MAXNSERVERS;
_3proxy_mutex_lock(&nservers_mutex);
if(n) memcpy(nservers, ns, n * sizeof(*ns));
numservers = n;
_3proxy_mutex_unlock(&nservers_mutex);
}
static int dnsqmatch(const unsigned char *a, const unsigned char *b, int len){ static int dnsqmatch(const unsigned char *a, const unsigned char *b, int len){
int i; int i;
@ -19,14 +44,12 @@ static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, un
int i,n; int i,n;
uint32_t retval; uint32_t retval;
struct nserver ns[MAXNSERVERS];
if((af == AF_INET) && (retval = hashresolv(&dns_table, name, value, retttl))) { if((af == AF_INET || af == AF_INET6) && (retval = nscacheresolv(af, name, value, retttl))) {
return retval; return retval;
} }
if((af == AF_INET6) && (retval = hashresolv(&dns6_table, name, value, retttl))) { n = (makeauth && !SAISNULL(&c->authnserver.addr))? 1 : getnservers(ns);
return retval;
}
n = (makeauth && !SAISNULL(&c->authnserver.addr))? 1 : c->numservers;
for(i=0; i<n; i++){ for(i=0; i<n; i++){
unsigned short nq, na; unsigned short nq, na;
unsigned char b[4098], *buf, *s1, *s2; unsigned char b[4098], *buf, *s1, *s2;
@ -54,8 +77,8 @@ static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, un
*SAFAMILY(sinsl) = *SAFAMILY(&c->authnserver.addr); *SAFAMILY(sinsl) = *SAFAMILY(&c->authnserver.addr);
} }
else { else {
usetcp = c->nservers[i].usetcp; usetcp = ns[i].usetcp;
*SAFAMILY(sinsl) = *SAFAMILY(&c->nservers[i].addr); *SAFAMILY(sinsl) = *SAFAMILY(&ns[i].addr);
} }
if((sock=so._socket(so.state, SASOCK(sinsl), usetcp?SOCK_STREAM:SOCK_DGRAM, usetcp?IPPROTO_TCP:IPPROTO_UDP)) == INVALID_SOCKET) break; 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))){ if(so._bind(so.state, sock,(struct sockaddr *)sinsl,SASIZE(sinsl))){
@ -67,7 +90,7 @@ static uint32_t udpresolvec(struct extparam *c, int af, unsigned char * name, un
*sinsr = c->authnserver.addr; *sinsr = c->authnserver.addr;
} }
else { else {
*sinsr = c->nservers[i].addr; *sinsr = ns[i].addr;
} }
if(usetcp){ if(usetcp){
if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),c->timeouts[CONNECT_TO])) { if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),c->timeouts[CONNECT_TO])) {
@ -246,3 +269,359 @@ uint32_t fakeresolver (int af, unsigned char *name, unsigned char * value){
} }
return 1; return 1;
} }
/* The system's name servers and hosts file, the default for a configuration
without nserver (or fakeresolve): read at start, before a chroot can hide
them, and checked from the main loop for changes, which apply while the
default is in force. Hosts entries have tables of their own, emptied when
the default is not in force. Only the main thread works on this. */
#define HOSTSEXPIRES ((sizeof(time_t) > 4)? (time_t)0xfffffffe : (time_t)0x7ffffffe)
/* the time to live a hosts file name is given in an answer */
#define HOSTSTTL 60
#define SYSDNSCHECK 5
static struct {
struct nserver ns[MAXNSERVERS];
int n; /* as last read */
int active; /* the default in force */
char *hosts; /* the hosts file as last read */
struct stat hsb; /* and how it was then */
int hok;
char *names; /* hosts names imported: family, name, 0 */
size_t namesl, namess;
time_t checked;
} sysdns;
/* An address of a name server, put after the others unless one of them */
static int addns(struct nserver *ns, int n, const char *s){
PROXYSOCKADDRTYPE sa;
int i;
if(n >= MAXNSERVERS) return n;
memset(&sa, 0, sizeof(sa));
if(inet_pton(AF_INET, s, SAADDR(&sa)) > 0) *SAFAMILY(&sa) = AF_INET;
#ifndef NOIPV6
else if(inet_pton(AF_INET6, s, SAADDR(&sa)) > 0) *SAFAMILY(&sa) = AF_INET6;
#endif
else return n; /* a link-local one with %scope is not */
*SAPORT(&sa) = htons(53);
for(i = 0; i < n; i++)
if(!memcmp(&ns[i].addr, &sa, sizeof(sa))) return n;
memset(ns + n, 0, sizeof(*ns));
ns[n].addr = sa;
return n + 1;
}
/* Addresses in a list, separated by spaces or commas */
static int addnslist(struct nserver *ns, int n, const char *s){
char a[64];
int l;
while(*s && n < MAXNSERVERS){
while(*s == ' ' || *s == ',' || *s == '\t') s++;
for(l = 0; *s && *s != ' ' && *s != ',' && *s != '\t'; s++) if(l < 63) a[l++] = *s;
a[l] = 0;
if(l) n = addns(ns, n, a);
}
return n;
}
#ifdef _WIN32
/* A string value, a multi-string one as one list; 0 if there is none */
static int regstr(HKEY k, const char *name, char *buf, DWORD size){
DWORD type, len = size - 2, i;
if(RegQueryValueExA(k, name, NULL, &type, (LPBYTE)buf, &len) || (type != REG_SZ && type != REG_MULTI_SZ)) return 0;
buf[len] = buf[len + 1] = 0;
if(type == REG_MULTI_SZ) for(i = 0; i + 1 < len; i++) if(!buf[i]) buf[i] = ' ';
return *buf != 0;
}
/* Whether an interface has an address of its own, as it does unless
disconnected: what DHCP gave it or the first one set */
static int regup(HKEY k){
char buf[256];
if(regstr(k, "DhcpIPAddress", buf, sizeof(buf)) && strncmp(buf, "0.0.0.0", 7)) return 1;
return regstr(k, "IPAddress", buf, sizeof(buf)) && strncmp(buf, "0.0.0.0", 7);
}
/* The name servers of a key's interfaces: those set, or with dhcp those
DHCP gave; for IPv4 only of an interface with an address */
static int regifaces(struct nserver *ns, int n, const char *path, const char *value, int needup){
HKEY k, ik;
char name[256], buf[1024];
DWORD i, len;
if(RegOpenKeyExA(HKEY_LOCAL_MACHINE, path, 0, KEY_READ, &k)) return n;
for(i = 0; n < MAXNSERVERS; i++){
len = sizeof(name);
if(RegEnumKeyExA(k, i, name, &len, NULL, NULL, NULL, NULL)) break;
if(RegOpenKeyExA(k, name, 0, KEY_READ, &ik)) continue;
if((!needup || regup(ik)) && regstr(ik, value, buf, sizeof(buf))) n = addnslist(ns, n, buf);
RegCloseKey(ik);
}
RegCloseKey(k);
return n;
}
/* As Windows has them: set for the system, set for an interface, then
those DHCP gave */
static int readsysns(struct nserver *ns){
HKEY k;
char buf[1024];
int n = 0;
static const char tcpip[] = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
if(!RegOpenKeyExA(HKEY_LOCAL_MACHINE, tcpip, 0, KEY_READ, &k)){
if(regstr(k, "NameServer", buf, sizeof(buf))) n = addnslist(ns, n, buf);
RegCloseKey(k);
}
n = regifaces(ns, n, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces", "NameServer", 1);
#ifndef NOIPV6
n = regifaces(ns, n, "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters\\Interfaces", "NameServer", 0);
#endif
n = regifaces(ns, n, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces", "DhcpNameServer", 1);
if(n < MAXNSERVERS && !RegOpenKeyExA(HKEY_LOCAL_MACHINE, tcpip, 0, KEY_READ, &k)){
if(regstr(k, "DhcpNameServer", buf, sizeof(buf))) n = addnslist(ns, n, buf);
RegCloseKey(k);
}
return n;
}
static const char *hostspath(void){
static char path[MAX_PATH + 32];
UINT l;
if(!*path){
l = GetSystemDirectoryA(path, MAX_PATH);
if(!l || l >= MAX_PATH) strcpy(path, "C:\\Windows\\System32");
strcat(path, "\\drivers\\etc\\hosts");
}
return path;
}
#else
/* As resolv.conf has them, in its order */
static int readsysns(struct nserver *ns){
FILE *f;
char line[512], *s, *e;
int n = 0;
if(!(f = fopen("/etc/resolv.conf", "r"))) return 0;
while(n < MAXNSERVERS && fgets(line, sizeof(line), f)){
if(strncmp(line, "nameserver", 10) || (line[10] != ' ' && line[10] != '\t')) continue;
for(s = line + 10; *s == ' ' || *s == '\t'; s++);
for(e = s; *e && !isspace((unsigned char)*e) && *e != '#' && *e != ';'; e++);
*e = 0;
n = addns(ns, n, s);
}
fclose(f);
return n;
}
static const char *hostspath(void){
return "/etc/hosts";
}
#endif
static char *readhosts(struct stat *sb){
FILE *f;
char *buf;
long l;
if(stat(hostspath(), sb) || !(f = fopen(hostspath(), "rb"))) return NULL;
fseek(f, 0, SEEK_END);
l = ftell(f);
fseek(f, 0, SEEK_SET);
if(l < 0 || l > 64*1024*1024 || !(buf = malloc((size_t)l + 1))){
fclose(f);
return NULL;
}
l = (long)fread(buf, 1, (size_t)l, f);
buf[l] = 0;
fclose(f);
return buf;
}
/* The hosts names imported before taken out */
static void hostsremove(void){
size_t i;
for(i = 0; i < sysdns.namesl; i += strlen(sysdns.names + i + 1) + 2)
hashdelete(sysdns.names[i] == 6? &hosts6_table : &hosts_table, sysdns.names + i + 1);
sysdns.namesl = 0;
}
static void hostsname(int af, const char *name){
size_t l = strlen(name) + 2;
char *p;
if(sysdns.namesl + l > sysdns.namess){
size_t ns = (sysdns.namess + l) * 2;
if(!(p = realloc(sysdns.names, ns))) return;
sysdns.names = p;
sysdns.namess = ns;
}
sysdns.names[sysdns.namesl] = (char)af;
memcpy(sysdns.names + sysdns.namesl + 1, name, l - 1);
sysdns.namesl += l;
}
/* The hosts file imported: its first address of a family for a name, as
the C library takes it */
static void hostsimport(void){
char *s, *e, *next, *tok, addr[16], name[256];
struct hashtable *ht;
unsigned char old[16];
uint32_t ttl;
int af, l;
hostsremove();
if(!sysdns.hosts) return;
for(s = sysdns.hosts; *s; s = next){
for(e = s; *e && *e != '\n' && *e != '\r' && *e != '#'; e++);
for(next = e; *next && *next != '\n'; next++);
if(*next) next++;
/* the address */
while(s < e && (*s == ' ' || *s == '\t')) s++;
for(tok = s; s < e && *s != ' ' && *s != '\t'; s++);
if((l = (int)(s - tok)) <= 0 || l >= (int)sizeof(name)) continue;
memcpy(name, tok, l);
name[l] = 0;
if(inet_pton(AF_INET, name, addr) > 0) af = 4;
#ifndef NOIPV6
else if(inet_pton(AF_INET6, name, addr) > 0) af = 6;
#endif
else continue;
ht = (af == 6)? &hosts6_table : &hosts_table;
/* its names */
for(;;){
while(s < e && (*s == ' ' || *s == '\t')) s++;
if(s >= e) break;
for(tok = s; s < e && *s != ' ' && *s != '\t'; s++);
if((l = (int)(s - tok)) >= (int)sizeof(name)) continue;
for(l = 0; tok + l < s; l++) name[l] = (char)tolower((unsigned char)tok[l]);
name[l] = 0;
/* one before in the file */
if(hashresolv(ht, name, old, &ttl)) continue;
hashadd(ht, name, addr, HOSTSEXPIRES);
hostsname(af, name);
}
}
}
/* The system's name servers and hosts file read again; what cannot be read
now (after a chroot) or has no name server stays as it was. Returns 1 for
name servers changed, 2 for the hosts file, as a mask. */
static int sysdnsread(void){
struct nserver ns[MAXNSERVERS];
struct stat sb;
char *h;
int n, res = 0;
memset(ns, 0, sizeof(ns));
n = readsysns(ns);
if(n && (n != sysdns.n || memcmp(ns, sysdns.ns, n * sizeof(*ns)))){
memcpy(sysdns.ns, ns, sizeof(ns));
sysdns.n = n;
res |= 1;
}
if(!stat(hostspath(), &sb) && (!sysdns.hok || sb.st_mtime != sysdns.hsb.st_mtime || sb.st_size != sysdns.hsb.st_size
#ifndef _WIN32
|| sb.st_ino != sysdns.hsb.st_ino || sb.st_ctime != sysdns.hsb.st_ctime
#endif
) && (h = readhosts(&sb))){
free(sysdns.hosts);
sysdns.hosts = h;
sysdns.hsb = sb;
sysdns.hok = 1;
res |= 2;
}
return res;
}
/* Read at start, before the configuration and a chroot in it, and in force
while it is read: its host names are resolved with them, nserver lines
taking their place once it is committed */
void sysdnsinit(void){
_3proxy_mutex_init(&nservers_mutex);
inithashtable(&hosts_table, 256, 256, 1 << 22);
#ifndef NOIPV6
inithashtable(&hosts6_table, 256, 256, 1 << 22);
#endif
sysdnsread();
sysdns.checked = time(NULL);
if(!sysdns.n) return;
sysdns.active = 1;
setnservers(sysdns.ns, sysdns.n);
resolvfunc = myresolver;
hostsimport();
}
/* A name known without asking: one nsrecord made first, then the hosts
file while it is in force, then what name servers answered */
int nscacheresolv(int af, unsigned char *name, unsigned char *value, uint32_t *ttl){
struct hashtable *cache = (af == AF_INET6)? &dns6_table : &dns_table;
uint32_t t = 0;
int r;
r = hashresolv(cache, name, value, &t);
if(r && t > 3600*24*365){
if(ttl) *ttl = t;
return r;
}
if(sysdns.active && hashresolv((af == AF_INET6)? &hosts6_table : &hosts_table, name, value, NULL)){
if(ttl) *ttl = HOSTSTTL;
return 1;
}
if(r && ttl) *ttl = t;
return r;
}
int sysdnscount(void){
return sysdns.n;
}
/* A configuration committed: the name servers of its nserver lines, else,
with nothing else to resolve names, the system's and its hosts file, read
again. Until then those in force stay. */
void sysdnsapply(struct extparam *c, const struct nserver *ns, int n){
sysdns.active = 0;
if(n){
setnservers(ns, n);
hostsremove();
return;
}
if(c->resolvfunc){
setnservers(NULL, 0);
hostsremove();
return;
}
sysdnsread();
sysdns.checked = curtime;
sysdns.active = 1;
setnservers(sysdns.ns, sysdns.n);
if(!sysdns.n) return; /* the C library, as before */
c->resolvfunc = myresolver;
hostsimport();
}
/* From the main loop: name servers and hosts file read again while the
default is in force, every few seconds. What cannot be read (after a
chroot) is left as it was. */
void sysdnscheck(void){
int was = sysdns.n, res;
if(!sysdns.active || curtime - sysdns.checked < SYSDNSCHECK) return;
sysdns.checked = curtime;
res = sysdnsread();
if(res & 1){
setnservers(sysdns.ns, sysdns.n);
dolog(NULL, (unsigned char *)"System name servers changed");
/* none before: the C library resolved until now */
if(!was) conf->resolvfunc = resolvfunc = myresolver;
}
if(resolvfunc == myresolver && ((res & 2) || !was)) hostsimport();
if(res & 2) dolog(NULL, (unsigned char *)"Hosts file changed");
}

View File

@ -502,7 +502,7 @@ struct nserver {
int usetcp; int usetcp;
}; };
#define MAXNSERVERS 5 #define MAXNSERVERS 3
#define MAXRADIUS 5 #define MAXRADIUS 5
struct radserver { struct radserver {
@ -919,8 +919,7 @@ struct extparam {
time_t logtime; time_t logtime;
unsigned logdumpsrv, logdumpcli; unsigned logdumpsrv, logdumpcli;
char delimchar; char delimchar;
struct nserver nservers[MAXNSERVERS], authnserver; struct nserver authnserver;
int numservers;
struct radserver radiuslist[MAXRADIUS]; struct radserver radiuslist[MAXRADIUS];
int nradservers; int nradservers;
char radiussecret[64]; char radiussecret[64];
@ -999,6 +998,8 @@ struct child {
extern struct hashtable dns_table; extern struct hashtable dns_table;
extern struct hashtable dns6_table; extern struct hashtable dns6_table;
extern struct hashtable hosts_table;
extern struct hashtable hosts6_table;
extern struct hashtable auth_table; extern struct hashtable auth_table;
extern const struct hashtable pwl_template; extern const struct hashtable pwl_template;

83
tests/cases/sysdns.py Normal file
View File

@ -0,0 +1,83 @@
"""The system's name servers and hosts file as the default resolver: with
no nserver in a configuration, names in the hosts file are answered from
it, through the name cache; with nserver lines, the file is not used."""
import os
import re
import sys
import time
def hosts_path():
if sys.platform == "win32":
return os.path.join(os.environ.get("SystemRoot", r"C:\Windows"),
"System32", "drivers", "etc", "hosts")
return "/etc/hosts"
def hosts_localhost():
"""Whether the hosts file maps localhost to 127.0.0.1 itself."""
try:
with open(hosts_path(), errors="replace") as f:
for line in f:
words = line.split("#", 1)[0].split()
if len(words) > 1 and words[0] == "127.0.0.1" and "localhost" in [w.lower() for w in words[1:]]:
return True
except OSError:
pass
return False
def system_nameserver():
"""Whether there is a name server for 3proxy to find: resolv.conf has
one, or on Windows the registry is taken to."""
if sys.platform == "win32":
return True
try:
with open("/etc/resolv.conf") as f:
return any(re.match(r"nameserver\s+[0-9a-fA-F.:]+\s*$", l.split("#")[0]) for l in f)
except OSError:
return False
def wait(t, port, name):
deadline = time.time() + 5
while time.time() < deadline:
if t.dns_query(port, name):
return
def run(t):
if not hosts_localhost() or not system_nameserver():
t.skip("no localhost in the hosts file, or no system name server")
return
port = t.free_port()
t.start("sysdns", f"""
log
flush
auth iponly
allow *
dnspr -p{port}
""")
wait(t, port, "localhost")
t.eq(["127.0.0.1"], t.dns_query(port, "localhost"),
"without nserver, a name of the hosts file is answered from it")
t.eq(["127.0.0.1"], t.dns_query(port, "LocalHost"),
"whatever its case")
# with a name server configured, one that answers nothing, the hosts
# file is not used
other = t.free_port()
t.start("sysdns_nserver", f"""
log
flush
nserver 127.0.0.1:{t.free_port()}
nscache 1024
auth iponly
allow *
dnspr -p{other}
""")
wait(t, other, "localhost")
t.ne(["127.0.0.1"], t.dns_query(other, "localhost") or [],
"with nserver, the hosts file is not used")