From 67a73a41322a597616b26815199e7d8f48a72383 Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Thu, 15 Oct 2026 16:02:14 +0300 Subject: [PATCH] Count the traffic of each listener A listener counts the traffic of its clients as it is logged: bytes from servers (%I) and from clients (%O). clearstat(), which every log writer calls once a line is logged, as well as lognone and logradius, adds the counts to the client's listener before clearing them, through listenertrafficfunc, set where services have listeners and NULL in the standalone binaries. The admin services page shows them as listenertrafin and listenertrafout. The takeover case checks that they go on through three reloads. Co-Authored-By: Claude Opus 5.5 --- src/3proxy.c | 1 + src/common.c | 1 + src/datatypes.c | 10 ++++++++++ src/log.c | 3 +++ src/proxy.h | 5 +++++ src/proxymain.c | 23 +++++++++++++++++++++++ tests/cases/takeover.py | 16 +++++++++++++--- 7 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/3proxy.c b/src/3proxy.c index 90e6e07..4559be2 100644 --- a/src/3proxy.c +++ b/src/3proxy.c @@ -559,6 +559,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int _3proxy_mutex_init(&confref_mutex); _3proxy_mutex_init(&listener_mutex); reattachfunc = reattach; + listenertrafficfunc = listenertraffic; _3proxy_mutex_init(&runningservices_mutex); _3proxy_mutex_init(&bandlim_mutex); _3proxy_mutex_init(&connlim_mutex); diff --git a/src/common.c b/src/common.c index 1d1f534..3b6f993 100644 --- a/src/common.c +++ b/src/common.c @@ -234,6 +234,7 @@ struct extparam confboot = { struct extparam *conf = &confboot; struct extparam *runconf = &confboot; int (*reattachfunc)(struct clientparam *param, int auth) = NULL; +void (*listenertrafficfunc)(struct clientparam *param) = NULL; /* Authorizes a request on a connection which may outlive a reload, an HTTP one kept alive: as a client of the service whose rules apply now, the diff --git a/src/datatypes.c b/src/datatypes.c index cf70981..6293511 100644 --- a/src/datatypes.c +++ b/src/datatypes.c @@ -538,6 +538,14 @@ static void * ef_server_listenerprocessed(struct node * node){ return listenerprocessed((struct srvparam *)node->value); } +static void * ef_server_listenertrafin(struct node * node){ + return listenertrafin((struct srvparam *)node->value); +} + +static void * ef_server_listenertrafout(struct node * node){ + return listenertrafout((struct srvparam *)node->value); +} + static void * ef_server_childcount(struct node * node){ return &((struct srvparam *)node->value) -> childcount; } @@ -896,6 +904,8 @@ static struct property prop_server[] = { {"childcount", ef_server_childcount, TYPE_INTEGER, "number of servers connected"}, {"listenerclients", ef_server_listenerclients, TYPE_UNSIGNED64, "clients connected to the listening socket, of its services old and new"}, {"listenerprocessed", ef_server_listenerprocessed, TYPE_UNSIGNED64, "clients of the listening socket done with"}, + {"listenertrafin", ef_server_listenertrafin, TYPE_UNSIGNED64, "bytes from servers to the clients of the listening socket, as logged"}, + {"listenertrafout", ef_server_listenertrafout, TYPE_UNSIGNED64, "bytes from the clients of the listening socket, as logged"}, {"maxchild", ef_server_maxchild, TYPE_INTEGER, "max concurrent connections"}, {"backlog", ef_server_backlog, TYPE_INTEGER, "listen backlog"}, {"needuser", ef_server_needuser, TYPE_INTEGER, "require user authentication"}, diff --git a/src/log.c b/src/log.c index b498556..db7e70f 100644 --- a/src/log.c +++ b/src/log.c @@ -25,6 +25,9 @@ void dolog(struct clientparam * param, const unsigned char *s){ void clearstat(struct clientparam * param) { + /* the traffic logged, or dropped, counted for the listener first */ + if(param->srv && listenertrafficfunc && (param->statssrv64 || param->statscli64)) + (*listenertrafficfunc)(param); #ifdef _WIN32 struct timeb tb; diff --git a/src/proxy.h b/src/proxy.h index 89cd6ee..66ca224 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -406,6 +406,11 @@ extern int (*reattachfunc)(struct clientparam *param, int auth); int clientauth(struct clientparam *param); uint64_t *listenerclients(struct srvparam *srv); uint64_t *listenerprocessed(struct srvparam *srv); +uint64_t *listenertrafin(struct srvparam *srv); +uint64_t *listenertrafout(struct srvparam *srv); +void listenertraffic(struct clientparam *param); +/* listenertraffic() where services have listeners, NULL elsewhere */ +extern void (*listenertrafficfunc)(struct clientparam *param); struct passwords * copypwl (struct passwords *pwl); void freepwl(struct passwords *pw); int copyfilter(struct filter *, struct srvparam *srv); diff --git a/src/proxymain.c b/src/proxymain.c index 2784b77..a8771e2 100644 --- a/src/proxymain.c +++ b/src/proxymain.c @@ -906,6 +906,9 @@ struct listener { /* its clients, whichever of its services, old or new, they came to: those connected now, and those done with */ uint64_t clients, processed; + /* the traffic of its clients as logged: from the server (%I) and + from the client (%O) */ + uint64_t trafin, trafout; _3proxy_mutex_t count_mutex; }; @@ -923,6 +926,18 @@ static void listenercount(struct srvparam *srv, int delta){ _3proxy_mutex_unlock(&l->count_mutex); } +/* The traffic of a client, about to be cleared once logged, counted for + its listener. */ +void listenertraffic(struct clientparam *param){ + struct listener *l = param->srv->listener; + + if(!l) return; + _3proxy_mutex_lock(&l->count_mutex); + l->trafin += param->statssrv64; + l->trafout += param->statscli64; + _3proxy_mutex_unlock(&l->count_mutex); +} + static uint64_t nocount = 0; /* The counters of a running service's listener, for the admin pages; the @@ -934,6 +949,14 @@ uint64_t *listenerclients(struct srvparam *srv){ uint64_t *listenerprocessed(struct srvparam *srv){ return srv->listener? &srv->listener->processed : &nocount; } + +uint64_t *listenertrafin(struct srvparam *srv){ + return srv->listener? &srv->listener->trafin : &nocount; +} + +uint64_t *listenertrafout(struct srvparam *srv){ + return srv->listener? &srv->listener->trafout : &nocount; +} static struct listener *listeners = NULL; _3proxy_mutex_t listener_mutex; diff --git a/tests/cases/takeover.py b/tests/cases/takeover.py index 6907d69..2da8ec1 100644 --- a/tests/cases/takeover.py +++ b/tests/cases/takeover.py @@ -76,15 +76,25 @@ def run(t): page = t.http(f"http://127.0.0.1:{adm}/S").text m = re.search(r"type of the service/clientlistenerclients.*?CDATA\[(\d+)\]" - r".*?listenerprocessed.*?CDATA\[(\d+)\]", page, re.S) - return (int(m.group(1)), int(m.group(2))) if m else (None, None) + r".*?listenerprocessed.*?CDATA\[(\d+)\]" + r".*?listenertrafin.*?CDATA\[(\d+)\]" + r".*?listenertrafout.*?CDATA\[(\d+)\]", page, re.S) + return tuple(int(v) for v in m.groups()) if m else (None,) * 4 - clients, processed = listener_counts("PROXY") + clients, processed, trafin, trafout = listener_counts("PROXY") t.eq(0, clients, "no client is connected to the proxy's listener after the run") total = done["ok"] + done["failed"] t.ok(f"its count of clients done with goes on through the reloads ({processed} of {total})") \ if processed is not None and processed >= total else \ t.fail("its count of clients done with goes on through the reloads", f"at least {total}", processed) + # and the traffic as logged: each answer brings 2000 bytes of body, each + # request a few dozen bytes + t.ok(f"the traffic from servers is counted through the reloads ({trafin} bytes)") \ + if trafin is not None and trafin >= 2000 * done["ok"] else \ + t.fail("the traffic from servers is counted through the reloads", f"at least {2000 * done['ok']}", trafin) + t.ok(f"and the traffic from clients ({trafout} bytes)") \ + if trafout is not None and trafout >= 50 * done["ok"] else \ + t.fail("and the traffic from clients", f"at least {50 * done['ok']}", trafout) # --- UDP: the socket is handed over too, and a session of the old # service goes on through it, its datagrams forwarded by the new one ---