diff --git a/src/3proxy.c b/src/3proxy.c index 3e4b10c..106e332 100644 --- a/src/3proxy.c +++ b/src/3proxy.c @@ -214,7 +214,7 @@ void dumpcounters(struct trafcount *tlin, int counterd){ if(cfp){ for(tl = tlin; cfp && tl; tl = tl->next){ if(tl->type >= conf.countertype) - fprintf(cfp, "%05d %020"PRIu64"%s%s\n", tl->number, tl->traf64, tl->comment?" #" : "", tl->comment? tl->comment : ""); + fprintf(cfp, "%05d %020"PRIu64"%s%s\n", tl->number, tl->v->traf64, tl->comment?" #" : "", tl->comment? tl->comment : ""); } fclose(cfp); } @@ -228,15 +228,15 @@ void dumpcounters(struct trafcount *tlin, int counterd){ if(lseek(counterd, sizeof(struct counter_header) + (tl->number - 1) * sizeof(struct counter_record), SEEK_SET) >= 0){ - crecord.traf64 = tl->traf64; - crecord.cleared = tl->cleared; - crecord.updated = tl->updated; + crecord.traf64 = tl->v->traf64; + crecord.cleared = tl->v->cleared; + crecord.updated = tl->v->updated; if(write(counterd, &crecord, sizeof(struct counter_record))){} } } - if(tl->type!=NEVER && timechanged(tl->cleared, curtime, tl->type)){ - tl->cleared = curtime; - tl->traf64 = 0; + if(tl->type!=NEVER && timechanged(tl->v->cleared, curtime, tl->type)){ + tl->v->cleared = curtime; + tl->v->traf64 = 0; } } } diff --git a/src/auth.c b/src/auth.c index 927e2ef..6de7e1e 100644 --- a/src/auth.c +++ b/src/auth.c @@ -47,12 +47,12 @@ int alwaysauth(struct clientparam * param){ countout = 1; if(tc->ace->action != COUNTALL) continue; } - if(tc->traflim64 <= tc->traf64) { + if(tc->traflim64 <= tc->v->traf64) { _3proxy_mutex_unlock(&tc_mutex); return 10; } param->trafcountfunc = conf.trafcountfunc; - param->maxtrafin64 = tc->traflim64 - tc->traf64; + param->maxtrafin64 = tc->traflim64 - tc->v->traf64; } } if(countout)for(tc = conf.trafcounter; tc; tc = tc->next) { @@ -62,12 +62,12 @@ int alwaysauth(struct clientparam * param){ if(tc->ace->action != COUNTOUT && tc->ace->action != COUNTALL) { continue; } - if(tc->traflim64 <= tc->traf64) { + if(tc->traflim64 <= tc->v->traf64) { _3proxy_mutex_unlock(&tc_mutex); return 10; } param->trafcountfunc = conf.trafcountfunc; - param->maxtrafout64 = tc->traflim64 - tc->traf64; + param->maxtrafout64 = tc->traflim64 - tc->v->traf64; } } _3proxy_mutex_unlock(&tc_mutex); diff --git a/src/conf.c b/src/conf.c index ddcd88a..be3bab6 100644 --- a/src/conf.c +++ b/src/conf.c @@ -47,6 +47,42 @@ int linenum = 0; struct counter_header cheader = {"3CF", (time_t)0}; struct counter_record crecord; +/* The values of the numbered counters, process-wide: a counter of a number + met for the first time takes its value from the counter file. Only the + thread reading the configuration adds to the list. */ +static struct trafcell { + struct trafcell *next; + unsigned number; + struct trafvalue v; +} *trafcells = NULL; + +struct trafvalue * trafvalue(unsigned number){ + struct trafcell *tc; + + for(tc = trafcells; tc; tc = tc->next) if(tc->number == number) return &tc->v; + if(!(tc = malloc(sizeof(*tc)))) return NULL; + memset(tc, 0, sizeof(*tc)); + tc->number = number; + if(conf.counterd >= 0) { + lseek(conf.counterd, + sizeof(struct counter_header) + (number - 1) * sizeof(struct counter_record), + SEEK_SET); + memset(&crecord, 0, sizeof(struct counter_record)); + if(read(conf.counterd, &crecord, sizeof(struct counter_record)) == sizeof(struct counter_record)){ + if(crecord.cleared < 0 || crecord.cleared >= MAX_COUNTER_TIME || crecord.updated < 0 || crecord.updated >= MAX_COUNTER_TIME){ + free(tc); + return NULL; + } + tc->v.traf64 = crecord.traf64; + tc->v.cleared = crecord.cleared; + tc->v.updated = crecord.updated; + } + } + tc->next = trafcells; + trafcells = tc; + return &tc->v; +} + int mainprepare(int argc, char** argv, void **service); int mainrun(void *service); void maindrop(void *service); @@ -1718,22 +1754,16 @@ static int h_ace(int argc, unsigned char **argv){ fprintf(stderr, "Wrong traffic limit specified, line %d\n", linenum); return(6); } - if(tl->number != 0 && conf.counterd >= 0) { - lseek(conf.counterd, - sizeof(struct counter_header) + (tl->number - 1) * sizeof(struct counter_record), - SEEK_SET); - memset(&crecord, 0, sizeof(struct counter_record)); - if(read(conf.counterd, &crecord, sizeof(struct counter_record)) == sizeof(struct counter_record)){ - tl->traf64 = crecord.traf64; - tl->cleared = crecord.cleared; - tl->updated = crecord.updated; - if(tl->cleared < 0 || tl->cleared >= MAX_COUNTER_TIME || tl->updated < 0 || tl->updated >= MAX_COUNTER_TIME){ - fprintf(stderr, "Invalid, incompatible or corrupted counter file.\n"); - return(6); - } - } + if(!tl->number) tl->v = &tl->own; + else if(!(tl->v = trafvalue(tl->number))){ + free(tl->comment); + free(tl); + freeacl(acl); + fprintf(stderr, "Invalid, incompatible or corrupted counter file.\n"); + return(6); } } + else tl->v = &tl->own; _3proxy_mutex_lock(&tc_mutex); if(!conf.trafcounter){ conf.trafcounter = tl; diff --git a/src/datatypes.c b/src/datatypes.c index 859bd33..84aa56e 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -471,7 +471,7 @@ static void * ef_trafcounter_type(struct node * node){ } static void * ef_trafcounter_traffic64(struct node * node){ - return &((struct trafcount *)node->value) -> traf64; + return &((struct trafcount *)node->value) -> v->traf64; } static void * ef_trafcounter_limit64(struct node * node){ return &((struct trafcount *)node->value) -> traflim64; @@ -493,11 +493,11 @@ static void * ef_client_bytesout64(struct node * node){ } static void * ef_trafcounter_cleared(struct node * node){ - return &((struct trafcount *)node->value) -> cleared; + return &((struct trafcount *)node->value) -> v->cleared; } static void * ef_trafcounter_updated(struct node * node){ - return &((struct trafcount *)node->value) -> updated; + return &((struct trafcount *)node->value) -> v->updated; } static void * ef_trafcounter_comment(struct node * node){ diff --git a/src/limiter.c b/src/limiter.c index be472da..3fe636a 100644 --- a/src/limiter.c +++ b/src/limiter.c @@ -184,8 +184,8 @@ void trafcountfunc(struct clientparam *param){ countout = 1; continue; } - tc->traf64 += param->statssrv64; - tc->updated = curtime; + tc->v->traf64 += param->statssrv64; + tc->v->updated = curtime; } } if(countout) for(tc = conf.trafcounter; tc; tc = tc->next) { @@ -194,8 +194,8 @@ void trafcountfunc(struct clientparam *param){ if(tc->ace->action != COUNTOUT && tc->ace->action != COUNTALL ) { continue; } - tc->traf64 += param->statscli64; - tc->updated = curtime; + tc->v->traf64 += param->statscli64; + tc->v->updated = curtime; } } diff --git a/src/plugins/LdapPlugin/ldapauth.c b/src/plugins/LdapPlugin/ldapauth.c index d5f57eb..0296b21 100644 --- a/src/plugins/LdapPlugin/ldapauth.c +++ b/src/plugins/LdapPlugin/ldapauth.c @@ -120,8 +120,8 @@ int savecounters(void *v) sprintf(tmpbuf,pat_file,ldap_dircount,tcd->ace->users->user); f=fopen(tmpbuf,"w+b"); fseek(f,0,SEEK_SET); - fprintf(f,"%"PRIu64" %lu %lu\n",tcd->traf64, - (unsigned long)tcd->cleared,(unsigned long)tcd->updated); + fprintf(f,"%"PRIu64" %lu %lu\n",tcd->v->traf64, + (unsigned long)tcd->v->cleared,(unsigned long)tcd->v->updated); fclose(f); free(tmpbuf); @@ -433,6 +433,7 @@ int h_trafgroup(int argc, unsigned char ** argv) newtrafcount =(*mypluginlink->mallocfunc)(sizeof (struct trafcount)); memset(newtrafcount, 0, sizeof(struct trafcount)); newtrafcount->ace = newace; + newtrafcount->v = &newtrafcount->own; newtrafcount->type=rtype; newtrafcount->traflim64 = traflimit; newtrafcount->comment=(*mypluginlink->strdupfunc)("ldapcounters"); @@ -450,9 +451,9 @@ int h_trafgroup(int argc, unsigned char ** argv) &rcounter.cleared, &rcounter.updated); - newtrafcount->traf64=rcounter.traf64; - newtrafcount->cleared=rcounter.cleared; - newtrafcount->updated=rcounter.updated; + newtrafcount->own.traf64=rcounter.traf64; + newtrafcount->own.cleared=rcounter.cleared; + newtrafcount->own.updated=rcounter.updated; fclose(f); } free(tmpbuf); diff --git a/src/proxy.h b/src/proxy.h index 47d0925..93bb893 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -291,6 +291,7 @@ int ipauth(struct clientparam * param); int doauth(struct clientparam * param); int strongauth(struct clientparam * param); void trafcountfunc(struct clientparam *param); +struct trafvalue * trafvalue(unsigned number); unsigned bandlimitfunc(struct clientparam *param, unsigned nbytesin, unsigned nbytesout); int handleredirect(struct clientparam * param, struct ace * acentry); diff --git a/src/structures.h b/src/structures.h index b84a9ce..6456533 100644 --- a/src/structures.h +++ b/src/structures.h @@ -471,17 +471,26 @@ struct schedule { }; +/* What a traffic counter has counted. A numbered counter's lives as long as + the process, shared by the counters of that number in every configuration + loaded, so that a reload neither loses it nor splits it; an unnumbered + counter keeps its own. */ +struct trafvalue { + uint64_t traf64; + time_t cleared; + time_t updated; +}; + struct trafcount { struct trafcount *next; struct ace *ace; unsigned number; ROTATION type; - uint64_t traf64; uint64_t traflim64; char * comment; int disabled; - time_t cleared; - time_t updated; + struct trafvalue *v; /* the shared value, or &own */ + struct trafvalue own; }; struct nserver { diff --git a/src/webadmin.c b/src/webadmin.c index 8683470..5d98119 100644 --- a/src/webadmin.c +++ b/src/webadmin.c @@ -458,16 +458,16 @@ int op_admin_counters(struct httpreq *r, const unsigned char *params) "%s", cp->traflim64 / (1024 * 1024), rotations[cp->type], - cp->traf64 / (1024 * 1024), - (((cp->traf64/16) *10) / (64*1024))%10, - cp->cleared?ctime(&cp->cleared):"never" + cp->v->traf64 / (1024 * 1024), + (((cp->v->traf64/16) *10) / (64*1024))%10, + cp->v->cleared?ctime(&cp->v->cleared):"never" ); inbuf += sprintf(buf + inbuf, "%s" "%i" "\r\n", - cp->updated?ctime(&cp->updated):"never", + cp->v->updated?ctime(&cp->v->updated):"never", cp->number ); } diff --git a/tests/cases/counters.py b/tests/cases/counters.py new file mode 100644 index 0000000..cfa2c3c --- /dev/null +++ b/tests/cases/counters.py @@ -0,0 +1,58 @@ +"""Traffic counters: a numbered counter keeps what it counted across a +reload, the value belonging to the process rather than to the configuration +read.""" + +import re +import time + + +def run(t): + origin = t.free_port() + proxy = t.free_port() + adm = t.free_port() + server = t.start("counters", f""" + log + auth iponly + allow * + http data * /data + httpsrv -p{origin} + + flush + auth iponly + allow * + countin 3/counted D 100 * * * + proxy -p{proxy} + admin -p{adm} + """, ports=[origin, proxy, adm]) + + def counted(): + """The counter's value in MB, as the counters page shows it.""" + page = t.http(f"http://127.0.0.1:{adm}/C").text + m = re.search(r"counted.*?([0-9]+\.[0-9])", page, re.S) + return float(m.group(1)) if m else None + + def wait_counted(least, timeout=5.0): + deadline = time.time() + timeout + value = counted() + while (value is None or value < least) and time.time() < deadline: + time.sleep(0.1) + value = counted() + return value + + reply = t.http(f"http://127.0.0.1:{origin}/data?size=300000", + proxy=f"127.0.0.1:{proxy}") + t.eq(300000, reply.length, "a body passes through the counted proxy") + before = wait_counted(0.2) + t.ok("the counter counts it") if before is not None and before >= 0.2 else \ + t.fail("the counter counts it", ">= 0.2", before) + + # reload, and wait for the services of the new configuration to run + out = server.output() + accepting = out.count("Accepting connections") + t.http(f"http://127.0.0.1:{adm}/R") + deadline = time.time() + 10 + while time.time() < deadline and server.output().count("Accepting connections") < accepting + 3: + time.sleep(0.05) + + after = wait_counted(0.2) + t.eq(before, after, "a reload keeps what the numbered counter counted")