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>
This commit is contained in:
Vladimir Dubrovin 2026-10-14 20:15:14 +03:00
parent 796e9f8156
commit 21ba6e9d0c
14 changed files with 399 additions and 112 deletions

View File

@ -73,13 +73,13 @@ void __stdcall CommandHandler( DWORD dwCommand )
case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_SHUTDOWN:
SetStatus( SERVICE_STOP_PENDING, 0, 1 ); SetStatus( SERVICE_STOP_PENDING, 0, 1 );
timetoexit = 1; timetoexit = 1;
conf->paused++; runconf->paused++;
/* ServiceMain() reports the service stopped once cyclestep() has /* ServiceMain() reports the service stopped once cyclestep() has
seen this and the services are done */ seen this and the services are done */
break; break;
case SERVICE_CONTROL_PAUSE: case SERVICE_CONTROL_PAUSE:
SetStatus( SERVICE_PAUSE_PENDING, 0, 1 ); SetStatus( SERVICE_PAUSE_PENDING, 0, 1 );
conf->paused++; runconf->paused++;
SetStatus( SERVICE_PAUSED, 0, 0 ); SetStatus( SERVICE_PAUSED, 0, 0 );
break; break;
case SERVICE_CONTROL_CONTINUE: case SERVICE_CONTROL_CONTINUE:
@ -120,9 +120,11 @@ void mysigusr1 (int sig){
int even = 0; int even = 0;
/* Signals may come while a reload reads the file: they act on the
configuration in force. */
void mysigpause (int sig){ void mysigpause (int sig){
conf->paused++; runconf->paused++;
even = !even; even = !even;
if(!even){ if(!even){
needreload = 1; needreload = 1;
@ -130,7 +132,7 @@ void mysigpause (int sig){
} }
void mysigterm (int sig){ void mysigterm (int sig){
conf->paused++; runconf->paused++;
timetoexit = 1; timetoexit = 1;
} }
@ -554,6 +556,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
nsopen(); /* before a chroot in the configuration takes /proc away */ nsopen(); /* before a chroot in the configuration takes /proc away */
#endif #endif
_3proxy_mutex_init(&config_mutex); _3proxy_mutex_init(&config_mutex);
_3proxy_mutex_init(&confref_mutex);
_3proxy_mutex_init(&runningservices_mutex); _3proxy_mutex_init(&runningservices_mutex);
_3proxy_mutex_init(&bandlim_mutex); _3proxy_mutex_init(&bandlim_mutex);
_3proxy_mutex_init(&connlim_mutex); _3proxy_mutex_init(&connlim_mutex);
@ -581,10 +584,11 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
httpsrv_init(); httpsrv_init();
#endif #endif
freeconf(conf); confbegin();
initcommands(); initcommands();
res = readconfig(fp); res = readconfig(fp);
conf->version++; conf->version++;
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
to 8 bits: a failed configuration exits with 1 */ to 8 bits: a failed configuration exits with 1 */

View File

@ -245,28 +245,30 @@ int strongauth(struct clientparam * param){
if (!*param->username) return 4; if (!*param->username) return 4;
if (!param->pwtype && *param->password) { if (!param->pwtype && *param->password) {
if (pwl_table.ihashtable && hashresolv(&pwl_table, param->username, pass, NULL)) { struct hashtable *pwl = &param->srv->config->pwl_table;
if (pwl->ihashtable && hashresolv(pwl, param->username, pass, NULL)) {
switch(pass[0]){ switch(pass[0]){
case CL: { case CL: {
int pwlen = strlen((char *)param->password); int pwlen = strlen((char *)param->password);
if(pwlen > 255) pwlen = 255; if(pwlen > 255) pwlen = 255;
if((unsigned)pwlen < pwl_table.recsize) { if((unsigned)pwlen < pwl->recsize) {
memset(buf, 0, pwl_table.recsize - 1); memset(buf, 0, pwl->recsize - 1);
memcpy(buf, param->password, pwlen); memcpy(buf, param->password, pwlen);
if(!ctmemcmp(pass + 1, buf, pwl_table.recsize - 1)) return 0; if(!ctmemcmp(pass + 1, buf, pwl->recsize - 1)) return 0;
} else { } else {
mdh_ctx *bctx; mdh_ctx *bctx;
unsigned hashsz; unsigned hashsz;
unsigned int blen; unsigned int blen;
hashsz = pwl_table.recsize - 1 < 64 ? pwl_table.recsize - 1 : 64; hashsz = pwl->recsize - 1 < 64 ? pwl->recsize - 1 : 64;
memset(buf, 0, pwl_table.recsize - 1); memset(buf, 0, pwl->recsize - 1);
bctx = mdh_init(MDH_BLAKE2, hashsz); bctx = mdh_init(MDH_BLAKE2, hashsz);
if(!bctx) return 6; if(!bctx) return 6;
mdh_update(bctx, param->password, pwlen + 1); mdh_update(bctx, param->password, pwlen + 1);
blen = hashsz; blen = hashsz;
mdh_final(bctx, buf, &blen); mdh_final(bctx, buf, &blen);
mdh_free(bctx); mdh_free(bctx);
if(!ctmemcmp(pass + 1, buf, pwl_table.recsize - 1)) return 0; if(!ctmemcmp(pass + 1, buf, pwl->recsize - 1)) return 0;
} }
return 6; return 6;
} }

View File

@ -228,7 +228,12 @@ const struct extparam confdefaults = {
struct extparam confboot = { struct extparam confboot = {
#include "confinit.h" #include "confinit.h"
}; };
/* conf is the configuration commands act on: the one being read during a
reload, the one in force otherwise. runconf is the one in force, for
whatever runs meanwhile without a service of its own. */
struct extparam *conf = &confboot; struct extparam *conf = &confboot;
struct extparam *runconf = &confboot;
_3proxy_mutex_t confref_mutex;
char* NULLADDR="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; char* NULLADDR="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

View File

@ -186,21 +186,40 @@ unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsign
return buf; return buf;
} }
static int deferred_add(void *service);
static int startservice(void *service);
/* The service is set up here, in the thread reading the configuration, so /* The service is set up here, in the thread reading the configuration, so
an error in its options stops the configuration; its own thread then an error in its options stops the configuration; its own thread then
listens and serves. */ listens and serves. During a reload it is started only once the whole
file has been read. */
int start_proxy_thread(struct child * chp){ int start_proxy_thread(struct child * chp){
pthread_t thread;
void *service; void *service;
int res; int res;
#ifdef _WIN32
HANDLE h;
#endif
if((res = mainprepare(chp->argc, (char **)chp->argv, &service))) { if((res = mainprepare(chp->argc, (char **)chp->argv, &service))) {
fprintf(stderr, "Service not started on line: %d%s\n", linenum, res == 2? ": insufficient memory": res == 3? ": too many filters": res == 4? ": network namespace unavailable":""); fprintf(stderr, "Service not started on line: %d%s\n", linenum, res == 2? ": insufficient memory": res == 3? ": too many filters": res == 4? ": network namespace unavailable":"");
return(40); return(40);
} }
switch(deferred_add(service)){
case 1:
return 0;
case -1:
maindrop(service);
fprintf(stderr, "Service not started on line: %d: insufficient memory\n", linenum);
return(40);
}
return startservice(service);
}
/* Starts the thread of a prepared service; 40 if it could not be. */
static int startservice(void *service){
pthread_t thread;
int res;
#ifdef _WIN32
HANDLE h;
#endif
_3proxy_mutex_lock(&runningservices_mutex); _3proxy_mutex_lock(&runningservices_mutex);
runningservices++; runningservices++;
_3proxy_mutex_unlock(&runningservices_mutex); _3proxy_mutex_unlock(&runningservices_mutex);
@ -244,7 +263,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(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize)){ if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){
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
@ -283,7 +302,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(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize)){ if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize)){
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
@ -349,7 +368,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(!resolvfunc || (resolvfunc == myresolver && !dns_table.poolsize) || resolvfunc == fakeresolver){ if(!conf->resolvfunc || (conf->resolvfunc == myresolver && !dns_table.poolsize) || 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
@ -396,7 +415,7 @@ static int h_log(int argc, unsigned char ** argv){
int notchanged = 0; int notchanged = 0;
havelog = 1; conf->havelog = 1;
if(argc > 1 && conf->logtarget && !strcmp((char *)conf->logtarget, (char *)argv[1])) { if(argc > 1 && conf->logtarget && !strcmp((char *)conf->logtarget, (char *)argv[1])) {
notchanged = 1; notchanged = 1;
} }
@ -620,8 +639,10 @@ static int h_users(int argc, unsigned char **argv){
*arg = 0; *arg = 0;
pw[0] = (char *)argv[j]; pw[0] = (char *)argv[j];
if (!pwl_table.ihashtable && inithashtable(&pwl_table, 16, 32, 1048576)) if (!conf->pwl_table.ihashtable){
return 3; conf->pwl_table = pwl_template;
if(inithashtable(&conf->pwl_table, 16, 32, 1048576)) return 3;
}
memset(pass, 0, sizeof(pass)); memset(pass, 0, sizeof(pass));
if (arg[1] && arg[2] && arg[3] == ':') { if (arg[1] && arg[2] && arg[3] == ':') {
pw[1] = (char *)(arg + 4); pw[1] = (char *)(arg + 4);
@ -646,12 +667,12 @@ static int h_users(int argc, unsigned char **argv){
} }
l = strlen(pw[1]); l = strlen(pw[1]);
if(l > 255) l = 255; if(l > 255) l = 255;
if((unsigned)l >= pwl_table.recsize) { if((unsigned)l >= conf->pwl_table.recsize) {
mdh_ctx *bctx; mdh_ctx *bctx;
unsigned hashsz; unsigned hashsz;
unsigned int blen; unsigned int blen;
if(*pass != CL) continue; if(*pass != CL) continue;
hashsz = pwl_table.recsize - 1 < 64 ? pwl_table.recsize - 1 : 64; hashsz = conf->pwl_table.recsize - 1 < 64 ? conf->pwl_table.recsize - 1 : 64;
bctx = mdh_init(MDH_BLAKE2, hashsz); bctx = mdh_init(MDH_BLAKE2, hashsz);
if(!bctx) continue; if(!bctx) continue;
mdh_update(bctx, pw[1], l + 1); mdh_update(bctx, pw[1], l + 1);
@ -661,7 +682,7 @@ static int h_users(int argc, unsigned char **argv){
} else { } else {
memcpy(pass + 1, pw[1], l); memcpy(pass + 1, pw[1], l);
} }
hashadd(&pwl_table, pw[0], pass, MAX_COUNTER_TIME); hashadd(&conf->pwl_table, pw[0], pass, MAX_COUNTER_TIME);
} }
return 0; return 0;
} }
@ -723,7 +744,7 @@ static int h_nserver(int argc, unsigned char **argv){
conf->numservers++; conf->numservers++;
} }
resolvfunc = myresolver; conf->resolvfunc = myresolver;
return 0; return 0;
} }
@ -742,7 +763,7 @@ static int h_authnserver(int argc, unsigned char **argv){
} }
static int h_fakeresolve(int argc, unsigned char **argv){ static int h_fakeresolve(int argc, unsigned char **argv){
resolvfunc = fakeresolver; conf->resolvfunc = fakeresolver;
return 0; return 0;
} }
@ -2392,6 +2413,8 @@ void freepwl(struct passwords *pwl){
} }
/* Releases what a configuration holds, once nothing uses it. Nothing global
is touched: a configuration may be released while another is in force. */
void freeconf(struct extparam *confp){ void freeconf(struct extparam *confp){
struct bandlim * bl; struct bandlim * bl;
struct bandlim * blout; struct bandlim * blout;
@ -2402,12 +2425,8 @@ void freeconf(struct extparam *confp){
int counterd, archiverc; int counterd, archiverc;
unsigned char **archiver; unsigned char **archiver;
unsigned char * logformat; unsigned char * logformat;
int i; int i;
_3proxy_mutex_lock(&tc_mutex); _3proxy_mutex_lock(&tc_mutex);
confp->trafcountfunc = NULL; confp->trafcountfunc = NULL;
tc = confp->trafcounter; tc = confp->trafcounter;
@ -2430,52 +2449,24 @@ void freeconf(struct extparam *confp){
confp->connlimiter = NULL; confp->connlimiter = NULL;
_3proxy_mutex_unlock(&connlim_mutex); _3proxy_mutex_unlock(&connlim_mutex);
destroyhashtable(&pwl_table); if(confp->pwl_table.ihashtable) destroyhashtable(&confp->pwl_table);
confp->logfunc = lognone; confp->logfunc = lognone;
logformat = confp->logformat; logformat = confp->logformat;
confp->logformat = NULL; confp->logformat = NULL;
confp->rotate = 0;
confp->logtype = NONE;
confp->logtime = curtime = 0;
confp->poolmax = -1;
confp->pooltimeout = 60;
archiverc = confp->archiverc; archiverc = confp->archiverc;
confp->archiverc = 0; confp->archiverc = 0;
archiver = confp->archiver; archiver = confp->archiver;
confp->archiver = NULL; confp->archiver = NULL;
fm = confp->fmon; fm = confp->fmon;
confp->fmon = NULL; confp->fmon = NULL;
confp->bandlimfunc = NULL;
memset(&confp->intsa, 0, sizeof(confp->intsa));
memset(&confp->extsa, 0, sizeof(confp->extsa));
#ifndef NOIPV6
memset(&confp->extsa6, 0, sizeof(confp->extsa6));
*SAFAMILY(&confp->extsa6) = AF_INET6;
#endif
*SAFAMILY(&confp->intsa) = AF_INET;
*SAFAMILY(&confp->extsa) = AF_INET;
confp->maxchild = DEFAULT_MAXCHILD;
confp->backlog = 0;
resolvfunc = NULL;
conf->numservers = 0;
acl = confp->acl; acl = confp->acl;
confp->acl = NULL; confp->acl = NULL;
usleep(SLEEPTIME);
{
char * args[] = {"auth", "iponly", NULL};
h_auth(2, (unsigned char **)args);
}
if(tc)dumpcounters(tc,counterd); if(tc)dumpcounters(tc,counterd);
for(; tc; tc = (struct trafcount *) itfree(tc, tc->next)){ for(; tc; tc = (struct trafcount *) itfree(tc, tc->next)){
if(tc->comment)free(tc->comment); if(tc->comment)free(tc->comment);
freeacl(tc->ace); freeacl(tc->ace);
} }
freeacl(acl); freeacl(acl);
for(; bl; bl = (struct bandlim *) itfree(bl, bl->next)) freeacl(bl->ace); for(; bl; bl = (struct bandlim *) itfree(bl, bl->next)) freeacl(bl->ace);
for(; blout; blout = (struct bandlim *) itfree(blout, blout->next))freeacl(blout->ace); for(; blout; blout = (struct bandlim *) itfree(blout, blout->next))freeacl(blout->ace);
@ -2494,14 +2485,110 @@ void freeconf(struct extparam *confp){
for(i = 0; i < archiverc; i++) free(archiver[i]); for(i = 0; i < archiverc; i++) free(archiver[i]);
free(archiver); free(archiver);
} }
havelog = 0; if(confp->authfuncs) freeauth(confp->authfuncs);
confp->authfuncs = NULL;
freepwl(confp->pwl);
confp->pwl = NULL;
#ifdef WITH_HTTPSRV
if(confp->httprules) freehttprules(confp->httprules);
confp->httprules = NULL;
#endif
if(confp->logname) free(confp->logname);
confp->logname = NULL;
if(confp->logtarget) free(confp->logtarget);
confp->logtarget = NULL;
if(confp->counterfile) free(confp->counterfile);
confp->counterfile = NULL;
if(confp->conffile) free(confp->conffile);
confp->conffile = NULL;
}
/* What every reading of the file starts from, before its first command. */
void confbegin(void){
char * args[] = {"auth", "iponly", NULL};
h_auth(2, (unsigned char **)args);
}
/* The configuration in force, held until confput(): for what uses one
without a service of its own, the resolver for one. */
struct extparam * confget(void){
struct extparam *c;
_3proxy_mutex_lock(&confref_mutex);
c = runconf;
c->refs++;
_3proxy_mutex_unlock(&confref_mutex);
return c;
}
void confhold(struct extparam *c){
_3proxy_mutex_lock(&confref_mutex);
c->refs++;
_3proxy_mutex_unlock(&confref_mutex);
}
/* The last to let go of a configuration no longer in force releases it. */
void confput(struct extparam *c){
int gone;
_3proxy_mutex_lock(&confref_mutex);
gone = (--c->refs <= 0 && c != runconf);
_3proxy_mutex_unlock(&confref_mutex);
if(gone){
freeconf(c);
if(c != &confboot) free(c);
}
}
/* During a reload the services are prepared as their lines are read, but
started only once the whole file has been read without an error. */
struct deferred {
void *service;
int linenum;
struct deferred *next;
};
static struct deferred *deferred = NULL;
static int deferring = 0;
/* Keeps a prepared service for later if a reload is reading the file: 1,
or 0 if none is, and the service is to be started now, -1 for want of
memory. The services start in the order of their lines. */
static int deferred_add(void *service){
struct deferred *d, **dp;
if(!deferring) return 0;
if(!(d = malloc(sizeof(*d)))) return -1;
d->service = service;
d->linenum = linenum;
d->next = NULL;
for(dp = &deferred; *dp; dp = &(*dp)->next);
*dp = d;
return 1;
} }
int reload (void){ int reload (void){
FILE *fp; FILE *fp;
int error = -2; int error = -2;
struct extparam *old = conf, *new;
struct deferred *d;
if(!(new = malloc(sizeof(*new)))) return -1;
*new = confdefaults;
/* what belongs to the process rather than to one reading of the file:
the open log, which a log command reopens, and the filters, a list
of static entries the TLS, PCRE and transparent code link in once */
new->conffile = old->conffile;
new->stringtable = old->stringtable;
new->demon = old->demon;
new->stdlog = old->stdlog;
new->filters = old->filters;
new->version = old->version + 1;
new->refs = 1; /* this reload's, until it is done with it */
_3proxy_mutex_lock(&config_mutex); _3proxy_mutex_lock(&config_mutex);
conf = new;
pluginlink.conf = new;
#ifdef WITH_SSL #ifdef WITH_SSL
ssl_install(); ssl_install();
#endif #endif
@ -2511,19 +2598,56 @@ int reload (void){
#ifdef WITH_TRANSPARENT #ifdef WITH_TRANSPARENT
transparent_install(); transparent_install();
#endif #endif
conf->paused++; confbegin();
freeconf(conf); deferring = 1;
conf->paused++;
fp = confopen(); fp = confopen();
if(fp){ if(fp){
error = readconfig(fp); error = readconfig(fp);
conf->version++;
if(error) {
freeconf(conf);
}
fclose(fp); fclose(fp);
} }
deferring = 0;
if(error){
/* the old configuration stays in force, its services running:
what was prepared for the new one is dropped */
while((d = deferred)){
deferred = d->next;
maindrop(d->service);
free(d);
}
old->conffile = new->conffile;
old->stdlog = new->stdlog;
old->filters = new->filters;
new->conffile = NULL;
new->stdlog = NULL;
new->filters = NULL;
new->stringtable = NULL;
conf = old;
pluginlink.conf = old;
_3proxy_mutex_unlock(&config_mutex); _3proxy_mutex_unlock(&config_mutex);
confput(new);
return error; return error;
} }
old->conffile = NULL;
old->stdlog = NULL;
old->filters = NULL;
old->stringtable = NULL;
confhold(old);
_3proxy_mutex_lock(&confref_mutex);
runconf = new;
_3proxy_mutex_unlock(&confref_mutex);
resolvfunc = new->resolvfunc;
/* the old services stop accepting, the new ones start */
old->paused++;
while((d = deferred)){
deferred = d->next;
linenum = d->linenum;
startservice(d->service);
free(d);
}
_3proxy_mutex_unlock(&config_mutex);
confput(new);
confput(old);
return 0;
}

View File

@ -514,7 +514,7 @@ static void * ef_server_next(struct node * node){
static void * ef_server_type(struct node * node){ static void * ef_server_type(struct node * node){
int service = ((struct srvparam *)node->value) -> service; int service = ((struct srvparam *)node->value) -> service;
return (service>=0 && service < MAX_SERVICE)? (void *)conf->stringtable[SERVICES + service] : (void *)"unknown"; return (service>=0 && service < MAX_SERVICE && runconf->stringtable)? (void *)runconf->stringtable[SERVICES + service] : (void *)"unknown";
} }
static void * ef_server_child(struct node * node){ static void * ef_server_child(struct node * node){
@ -543,7 +543,7 @@ static void * ef_pool_limit(struct node * node){
} }
static void * ef_pool_timeout(struct node * node){ static void * ef_pool_timeout(struct node * node){
return &conf->pooltimeout; return &runconf->pooltimeout;
} }
static void * ef_pool_created(struct node * node){ static void * ef_pool_created(struct node * node){
@ -693,7 +693,7 @@ static void * ef_client_next(struct node * node){
static void * ef_client_type(struct node * node){ static void * ef_client_type(struct node * node){
int service = ((struct clientparam *)node->value) -> service; int service = ((struct clientparam *)node->value) -> service;
return (service>=0 && service < MAX_SERVICE)? (void *)conf->stringtable[SERVICES + service] : (void *)"unknown"; return (service>=0 && service < MAX_SERVICE && runconf->stringtable)? (void *)runconf->stringtable[SERVICES + service] : (void *)"unknown";
} }
static void * ef_client_operation(struct node * node){ static void * ef_client_operation(struct node * node){

View File

@ -79,4 +79,5 @@ void param2hash_search(const struct hashtable *ht, void *index, uint8_t *hash){
struct hashtable dns_table = {char_index2hash, char_index2hash, 4, 32}; struct hashtable dns_table = {char_index2hash, char_index2hash, 4, 32};
struct hashtable dns6_table = {char_index2hash, char_index2hash, 16, 32}; struct hashtable dns6_table = {char_index2hash, char_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};
struct hashtable pwl_table = {char_index2hash, char_index2hash, 64, 64}; /* users: each configuration has a table of its own, made from this one */
const struct hashtable pwl_template = {char_index2hash, char_index2hash, 64, 64};

View File

@ -11,7 +11,6 @@
#include "proxy.h" #include "proxy.h"
_3proxy_mutex_t log_mutex; _3proxy_mutex_t log_mutex;
int havelog = 0;
struct clientparam logparam; struct clientparam logparam;
@ -69,7 +68,12 @@ int dobuf2(struct clientparam * param, unsigned char * buf, const unsigned char
struct timezone tz; struct timezone tz;
#endif #endif
if(!logparam.srv) srvinit(&logsrv, &logparam); if(!logparam.srv) {
srvinit(&logsrv, &logparam);
/* not a service: it holds no configuration, and the first one
is never freed, only what it holds */
logsrv.config = &confboot;
}
if(!param) param = &logparam; if(!param) param = &logparam;
@ -189,9 +193,9 @@ int dobuf2(struct clientparam * param, unsigned char * buf, const unsigned char
case 'N': case 'N':
if(param->service <= MAX_SERVICE) { if(param->service <= MAX_SERVICE) {
len = (conf->stringtable)? (int)strlen((char *)conf->stringtable[SERVICES + param->service]) : 0; len = (runconf->stringtable)? (int)strlen((char *)runconf->stringtable[SERVICES + param->service]) : 0;
if(len > 20) len = 20; if(len > 20) len = 20;
memcpy(buf+i, (len)?conf->stringtable[SERVICES + param->service]:(unsigned char*)"-", (len)?len:1); memcpy(buf+i, (len)?runconf->stringtable[SERVICES + param->service]:(unsigned char*)"-", (len)?len:1);
i += (len)?len:1; i += (len)?len:1;
} }
break; break;
@ -345,11 +349,11 @@ void logstdout(struct clientparam * param, const unsigned char *s) {
/* with no connection, an error from the main thread */ /* with no connection, an error from the main thread */
tmpbuf = param? param->tmpbuf : logparam.tmpbuf; tmpbuf = param? param->tmpbuf : logparam.tmpbuf;
dobuf(param, tmpbuf, s, NULL); dobuf(param, tmpbuf, s, NULL);
log = (param && param->srv && param->srv->stdlog)?param->srv->stdlog:conf->stdlog?conf->stdlog:stdout; log = (param && param->srv && param->srv->stdlog)?param->srv->stdlog:runconf->stdlog?runconf->stdlog:stdout;
if(!param || !param->nolog)if(fprintf(log, "%s\n", tmpbuf) < 0) { if(!param || !param->nolog)if(fprintf(log, "%s\n", tmpbuf) < 0) {
perror("printf()"); perror("printf()");
}; };
if(log != conf->stdlog)fflush(log); if(log != runconf->stdlog)fflush(log);
} }
#ifndef _WIN32 #ifndef _WIN32
void logsyslog(struct clientparam * param, const unsigned char *s) { void logsyslog(struct clientparam * param, const unsigned char *s) {

View File

@ -1048,7 +1048,7 @@ for(;;){
hlen += sprintf((char*)buf + hlen, "Via: 1.1 "); hlen += sprintf((char*)buf + hlen, "Via: 1.1 ");
gethostname((char *)(buf + hlen), 256); gethostname((char *)(buf + hlen), 256);
hlen += (int)strlen((char *)(buf + hlen)); hlen += (int)strlen((char *)(buf + hlen));
hlen += sprintf((char*)buf + hlen, ":%d (%s %s)\r\nX-Forwarded-For: ", (int)ntohs(*SAPORT(&param->srv->intsa)), conf->stringtable?conf->stringtable[2]:(unsigned char *)"", conf->stringtable?conf->stringtable[3]:(unsigned char *)""); hlen += sprintf((char*)buf + hlen, ":%d (%s %s)\r\nX-Forwarded-For: ", (int)ntohs(*SAPORT(&param->srv->intsa)), runconf->stringtable?runconf->stringtable[2]:(unsigned char *)"", runconf->stringtable?runconf->stringtable[3]:(unsigned char *)"");
if(anonymous != 2)hlen += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), (char *)buf + hlen, 128); if(anonymous != 2)hlen += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), (char *)buf + hlen, 128);
else { else {
uint32_t tmp = myrand(); uint32_t tmp = myrand();

View File

@ -225,7 +225,11 @@ extern time_t basetime;
extern int timetoexit, needreload; extern int timetoexit, needreload;
extern time_t curtime; /* the clock, kept by the main thread */ extern time_t curtime; /* the clock, kept by the main thread */
extern struct extparam *conf; extern struct extparam *conf, *runconf;
extern _3proxy_mutex_t confref_mutex;
struct extparam * confget(void);
void confhold(struct extparam *c);
void confput(struct extparam *c);
extern const struct extparam confdefaults; extern const struct extparam confdefaults;
extern struct extparam confboot; extern struct extparam confboot;
@ -383,7 +387,6 @@ 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 int havelog;
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);
@ -393,6 +396,7 @@ void freeacl(struct ace *ac);
void freeauth(struct auth *); void freeauth(struct auth *);
void freefilter(struct filter *filter); void freefilter(struct filter *filter);
void freeconf(struct extparam *confp); void freeconf(struct extparam *confp);
void confbegin(void);
struct passwords * copypwl (struct passwords *pwl); struct passwords * copypwl (struct passwords *pwl);
void freepwl(struct passwords *pw); void freepwl(struct passwords *pw);
int copyfilter(struct filter *, struct srvparam *srv); int copyfilter(struct filter *, struct srvparam *srv);

View File

@ -148,7 +148,7 @@ static void freeworker(struct clientparam *param){
static unsigned poollimit(void){ static unsigned poollimit(void){
unsigned limit; unsigned limit;
if(conf->poolmax >= 0) limit = (unsigned)conf->poolmax; if(runconf->poolmax >= 0) limit = (unsigned)runconf->poolmax;
else { else {
limit = 32 * tpool.services; limit = 32 * tpool.services;
if(limit > tpool.maxchild) limit = tpool.maxchild; if(limit > tpool.maxchild) limit = tpool.maxchild;
@ -173,7 +173,7 @@ static int poolpark(struct clientparam *param){
tpool.idle = param; tpool.idle = param;
tpool.nidle++; tpool.nidle++;
param->pooled = 1; param->pooled = 1;
timeout = conf->pooltimeout; timeout = runconf->pooltimeout;
_3proxy_mutex_unlock(&tpool.mutex); _3proxy_mutex_unlock(&tpool.mutex);
if(timeout <= 0) _3proxy_sem_lock(param->wake); if(timeout <= 0) _3proxy_sem_lock(param->wake);
@ -400,7 +400,7 @@ static void srvdiscard(struct srvstart *s){
static void srvunlink(struct srvparam *srv){ static void srvunlink(struct srvparam *srv){
if(srv->next)srv->next->prev = srv->prev; if(srv->next)srv->next->prev = srv->prev;
if(srv->prev)srv->prev->next = srv->next; if(srv->prev)srv->prev->next = srv->next;
else conf->services = srv->next; else srv->config->services = srv->next;
srv->next = srv->prev = NULL; srv->next = srv->prev = NULL;
} }
#endif #endif
@ -479,6 +479,9 @@ static int srvprepare(struct srvstart *s, int argc, char **argv){
#endif #endif
#endif #endif
srvinit(srv, defparam); srvinit(srv, defparam);
#ifndef STDMAIN
confhold(srv->config); /* until srvfree() */
#endif
srv->pf = childdef.pf; srv->pf = childdef.pf;
s->isudp = childdef.isudp; s->isudp = childdef.isudp;
@ -1486,7 +1489,7 @@ void srvinit(struct srvparam * srv, struct clientparam *param){
memset(srv, 0, sizeof(struct srvparam)); memset(srv, 0, sizeof(struct srvparam));
srv->version = conf->version + 1; srv->version = conf->version + 1;
srv->paused = conf->paused; srv->paused = conf->paused;
srv->logfunc = havelog?conf->logfunc:lognone; srv->logfunc = conf->havelog?conf->logfunc:lognone;
srv->noforce = conf->noforce; srv->noforce = conf->noforce;
srv->logformat = conf->logformat? (unsigned char *)strdup((char *)conf->logformat) : NULL; srv->logformat = conf->logformat? (unsigned char *)strdup((char *)conf->logformat) : NULL;
srv->authfunc = conf->authfunc; srv->authfunc = conf->authfunc;
@ -1500,7 +1503,7 @@ void srvinit(struct srvparam * srv, struct clientparam *param){
srv->maxseg = conf->maxseg; srv->maxseg = conf->maxseg;
srv->delimchar = conf->delimchar; srv->delimchar = conf->delimchar;
srv->time_start = time(NULL); srv->time_start = time(NULL);
if(havelog && conf->logtarget){ if(conf->havelog && conf->logtarget){
srv->logtarget = (unsigned char *)strdup((char *)conf->logtarget); srv->logtarget = (unsigned char *)strdup((char *)conf->logtarget);
} }
srv->srvsock = INVALID_SOCKET; srv->srvsock = INVALID_SOCKET;
@ -1631,6 +1634,15 @@ void srvfree(struct srvparam * srv){
#ifndef NOUDPMAIN #ifndef NOUDPMAIN
if(srv->udpbuf) free(srv->udpbuf); if(srv->udpbuf) free(srv->udpbuf);
if(srv->udpbuf2) free(srv->udpbuf2); if(srv->udpbuf2) free(srv->udpbuf2);
#endif
/* the last service of a configuration no longer in force releases it */
#ifndef STDMAIN
if(srv->config){
struct extparam *c = srv->config;
srv->config = NULL;
confput(c);
}
#endif #endif
} }

View File

@ -15,7 +15,7 @@ static int dnsqmatch(const unsigned char *a, const unsigned char *b, int len){
} }
uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_t *retttl, struct clientparam* param, int makeauth){ 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; int i,n;
uint32_t retval; uint32_t retval;
@ -26,7 +26,7 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
if((af == AF_INET6) && (retval = hashresolv(&dns6_table, name, value, retttl))) { if((af == AF_INET6) && (retval = hashresolv(&dns6_table, name, value, retttl))) {
return retval; return retval;
} }
n = (makeauth && !SAISNULL(&conf->authnserver.addr))? 1 : conf->numservers; 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;
@ -49,13 +49,13 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
memset(sinsr, 0, sizeof(addr)); memset(sinsr, 0, sizeof(addr));
if(makeauth && !SAISNULL(&conf->authnserver.addr)){ if(makeauth && !SAISNULL(&c->authnserver.addr)){
usetcp = conf->authnserver.usetcp; usetcp = c->authnserver.usetcp;
*SAFAMILY(sinsl) = *SAFAMILY(&conf->authnserver.addr); *SAFAMILY(sinsl) = *SAFAMILY(&c->authnserver.addr);
} }
else { else {
usetcp = conf->nservers[i].usetcp; usetcp = c->nservers[i].usetcp;
*SAFAMILY(sinsl) = *SAFAMILY(&conf->nservers[i].addr); *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((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))){
@ -63,14 +63,14 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
so._closesocket(so.state, sock); so._closesocket(so.state, sock);
break; break;
} }
if(makeauth && !SAISNULL(&conf->authnserver.addr)){ if(makeauth && !SAISNULL(&c->authnserver.addr)){
*sinsr = conf->authnserver.addr; *sinsr = c->authnserver.addr;
} }
else { else {
*sinsr = conf->nservers[i].addr; *sinsr = c->nservers[i].addr;
} }
if(usetcp){ if(usetcp){
if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),conf->timeouts[CONNECT_TO])) { if(connectwithpoll(NULL, sock,(struct sockaddr *)sinsr,SASIZE(sinsr),c->timeouts[CONNECT_TO])) {
so._shutdown(so.state, sock, SHUT_RDWR); so._shutdown(so.state, sock, SHUT_RDWR);
so._closesocket(so.state, sock); so._closesocket(so.state, sock);
break; break;
@ -115,13 +115,13 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
len+=2; len+=2;
} }
if(socksendto(NULL, sock, (struct sockaddr *)sinsr, buf, len, conf->timeouts[SINGLEBYTE_L]*1000) != len){ if(socksendto(NULL, sock, (struct sockaddr *)sinsr, buf, len, c->timeouts[SINGLEBYTE_L]*1000) != len){
so._shutdown(so.state, sock, SHUT_RDWR); so._shutdown(so.state, sock, SHUT_RDWR);
so._closesocket(so.state, sock); so._closesocket(so.state, sock);
continue; continue;
} }
if(param) param->statscli64 += len; if(param) param->statscli64 += len;
len = sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf, 4096, conf->timeouts[DNS_TO]*1000); len = sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf, 4096, c->timeouts[DNS_TO]*1000);
if(len > 13 && usetcp){ if(len > 13 && usetcp){
unsigned short us; unsigned short us;
us = ntohs(*(unsigned short*)buf); us = ntohs(*(unsigned short*)buf);
@ -129,7 +129,7 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
buf+=2; buf+=2;
if(us > 4096 || us < len) len = 0; if(us > 4096 || us < len) len = 0;
else if(us > len){ else if(us > len){
if(sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf+len, us-len, conf->timeouts[DNS_TO]*1000) != us-len) len = 0; if(sockrecvfrom(NULL, sock, (struct sockaddr *)&from, buf+len, us-len, c->timeouts[DNS_TO]*1000) != us-len) len = 0;
else len = us; else len = us;
} }
} }
@ -202,13 +202,32 @@ uint32_t udpresolve(int af, unsigned char * name, unsigned char * value, uint32_
/* a name longer than the field is no name to authenticate */ /* a name longer than the field is no name to authenticate */
if(PARAMSTR(param, username, buf + k + 13)) return 0; if(PARAMSTR(param, username, buf + k + 13)) return 0;
return udpresolve(af,param->username, value, NULL, NULL, 2); return udpresolvec(c, af,param->username, value, NULL, NULL, 2);
} }
} }
} }
return 0; 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){ uint32_t myresolver(int af, unsigned char * name, unsigned char * value){
return udpresolve(af, name, value, NULL, NULL, 0); return udpresolve(af, name, value, NULL, NULL, 0);
} }

View File

@ -909,6 +909,12 @@ struct extparam {
struct radserver radiuslist[MAXRADIUS]; struct radserver radiuslist[MAXRADIUS];
int nradservers; int nradservers;
char radiussecret[64]; char radiussecret[64];
/* the services which use it and whoever else holds it (confhold()):
freed when none does and it is no longer the one in force */
int refs;
struct hashtable pwl_table; /* users */
int havelog; /* log was configured */
RESOLVFUNC resolvfunc; /* nserver or fakeresolve, in force once committed */
}; };
struct property { struct property {
@ -974,7 +980,7 @@ 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 auth_table; extern struct hashtable auth_table;
extern struct hashtable pwl_table; extern const struct hashtable pwl_template;
extern struct hashtable udp_table; extern struct hashtable udp_table;
struct authcache { struct authcache {

View File

@ -353,9 +353,9 @@ static char * admin_open(struct printparam *pp, struct clientparam *param)
buf = malloc(LINESIZE); buf = malloc(LINESIZE);
if(!buf) return NULL; if(!buf) return NULL;
sprintf(buf, ok, conf->stringtable?(char *)conf->stringtable[2]:"3proxy", sprintf(buf, ok, runconf->stringtable?(char *)runconf->stringtable[2]:"3proxy",
conf->stringtable?(char *)conf->stringtable[2]:"3[APA3A] tiny proxy", runconf->stringtable?(char *)runconf->stringtable[2]:"3[APA3A] tiny proxy",
conf->stringtable?(char *)conf->stringtable[3]:""); runconf->stringtable?(char *)runconf->stringtable[3]:"");
printstr(pp, buf); printstr(pp, buf);
return buf; return buf;
} }
@ -374,7 +374,7 @@ int op_admin(struct httpreq *r, const unsigned char *params)
buf = admin_open(&pp, r->param); buf = admin_open(&pp, r->param);
if(!buf) return 1; if(!buf) return 1;
printstr(&pp, (char *)conf->stringtable[WEBBANNERS]); printstr(&pp, (char *)runconf->stringtable[WEBBANNERS]);
admin_close(&pp, buf); admin_close(&pp, buf);
return 0; return 0;
} }
@ -398,7 +398,7 @@ int op_admin_counters(struct httpreq *r, const unsigned char *params)
{ {
struct trafcount *cp; struct trafcount *cp;
int num = 0; int num = 0;
for(cp = conf->trafcounter; cp; cp = cp->next, num++){ for(cp = runconf->trafcounter; cp; cp = cp->next, num++){
int inbuf = 0; int inbuf = 0;
if(cp->ace && (limited || param->redirected)){ if(cp->ace && (limited || param->redirected)){
@ -489,7 +489,7 @@ int op_admin_reload(struct httpreq *r, const unsigned char *params)
buf = admin_open(&pp, r->param); buf = admin_open(&pp, r->param);
if(!buf) return 1; if(!buf) return 1;
if(r->param->srv->s_option) printstr(&pp, (char *)conf->stringtable[WEBBANNERS]); if(r->param->srv->s_option) printstr(&pp, (char *)runconf->stringtable[WEBBANNERS]);
else { else {
needreload = 1; needreload = 1;
printstr(&pp, "<h3>Reload scheduled</h3>"); printstr(&pp, "<h3>Reload scheduled</h3>");
@ -520,7 +520,10 @@ int op_admin_services(struct httpreq *r, const unsigned char *params)
printstr(&pp, "<threadpool>"); printstr(&pp, "<threadpool>");
printval(&tpool, TYPE_THREADPOOL, 0, &pp); printval(&tpool, TYPE_THREADPOOL, 0, &pp);
printstr(&pp, "</threadpool>\r\n"); printstr(&pp, "</threadpool>\r\n");
printval(conf->services, TYPE_SERVER, 0, &pp); /* services unlink themselves under config_mutex */
_3proxy_mutex_lock(&config_mutex);
printval(runconf->services, TYPE_SERVER, 0, &pp);
_3proxy_mutex_unlock(&config_mutex);
printstr(&pp, postxml); printstr(&pp, postxml);
} }

103
tests/cases/reload.py Normal file
View File

@ -0,0 +1,103 @@
"""Reloading the configuration: a file with an error leaves the configuration
in force, services and users, as it was; a good one replaces it, while the
connections of the old services finish undisturbed."""
import threading
import time
def config(origin, proxy, adm, user, extra=""):
return f"""
log
auth iponly
allow *
http data * /data
httpsrv -p{origin}
flush
users {user}
auth strong
allow *
proxy -p{proxy}
flush
auth iponly
allow *
admin -p{adm}
{extra}
"""
def run(t):
origin = t.free_port()
first = t.free_port()
second = t.free_port()
adm = t.free_port()
server = t.start("reload", config(origin, first, adm, "alice:CL:one"),
ports=[origin, first, adm])
url = f"http://127.0.0.1:{origin}/data?size=1000"
proxy = f"127.0.0.1:{first}"
def as_user(user, password, through=proxy):
return t.http(url, proxy=through, proxy_auth=(user, password)).status
t.eq(200, as_user("alice", "one"), "the first configuration lets alice in")
def reload_and_wait(expect_failure=False):
before = server.output()
accepting = before.count("Accepting connections")
t.http(f"http://127.0.0.1:{adm}/R")
deadline = time.time() + 10
while time.time() < deadline:
out = server.output()
if expect_failure and "Unknown command" in out[len(before):]:
return out
if not expect_failure and out.count("Accepting connections") >= accepting + 3:
return out
time.sleep(0.05)
return server.output()
# --- a file with an error: nothing changes --------------------------
t.write_config("reload", config(origin, first, adm, "bob:CL:two", "nosuchcommand"))
out = reload_and_wait(expect_failure=True)
t.contains(out, "Unknown command", "the broken configuration is refused")
time.sleep(1.5) # what a switch would take: the old services stop within a second
t.eq(200, as_user("alice", "one"), "after a failed reload alice still gets in")
t.ne(200, as_user("bob", "two"), "and bob, from the refused file, does not")
# --- a good file: the new configuration takes over -------------------
slow = {}
def download():
# about two seconds: 2 MB in 64 KB blocks, 60 ms apart
r = t.http(f"http://127.0.0.1:{origin}/data?size=2000000&block=65536&delay=60",
proxy=proxy, proxy_auth=("alice", "one"))
slow["status"], slow["length"] = r.status, r.length
th = threading.Thread(target=download)
th.start()
time.sleep(0.3)
t.write_config("reload", config(origin, second, adm, "bob:CL:two"))
reload_and_wait()
# The old services stop accepting within a second; until then both the
# old and the new origin listen, and a connection the old one has queued
# is reset when it closes (a listener is not handed over yet).
time.sleep(1.5)
t.ok("the new services start") if t.wait_port(second) else \
t.fail("the new services start", f"port {second} open", "closed")
moved = f"127.0.0.1:{second}"
t.eq(200, as_user("bob", "two", moved), "the new configuration lets bob in")
t.ne(200, as_user("alice", "one", moved), "and not alice, who is gone from it")
# the old service stops accepting, its port closes
deadline = time.time() + 5
while time.time() < deadline and t.wait_port(first, timeout=0.2):
time.sleep(0.2)
t.ok("the old service stops listening") if not t.wait_port(first, timeout=0.2) else \
t.fail("the old service stops listening", f"port {first} closed", "open")
th.join(20)
t.eq(200, slow.get("status"), "a download through the old service finishes")
t.eq(2000000, slow.get("length"), "with the whole answer")