mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-17 17:05:48 +08:00
Apply the new configuration to UDP sessions and kept-alive requests
UDP sessions check for a reload in udpsockmap() as TCP connections do in sockmap(): unless noforce, a session goes by the newest service on its listener, re-authenticated as its client, or is dropped. A request on a connection that outlives a reload, an HTTP one kept alive, is authorized by clientauth(): it moves the connection to the newest service on its listener first if a reload came since, then authorizes as the service whose rules apply (PSRV). The proxy uses it for every request, so a request of a user the new configuration removed is answered 407 rather than authorized by the old rules and cut while relayed. reattach() takes whether to authenticate: sockmap() and udpsockmap() move and authenticate at once, clientauth() moves and then authorizes the request. A test case covers the drops: with one slot free in the new service one of two downloads moves on and the other is cut (27; a service keeps one of its maxconn slots for the next connection it accepts), and a proxy download is cut when a socks service takes its port (26). The reload case checks the kept-alive request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
df09565fe4
commit
ae27a5cf90
21
src/common.c
21
src/common.c
@ -233,7 +233,26 @@ struct extparam confboot = {
|
|||||||
whatever runs meanwhile without a service of its own. */
|
whatever runs meanwhile without a service of its own. */
|
||||||
struct extparam *conf = &confboot;
|
struct extparam *conf = &confboot;
|
||||||
struct extparam *runconf = &confboot;
|
struct extparam *runconf = &confboot;
|
||||||
int (*reattachfunc)(struct clientparam *param) = NULL;
|
int (*reattachfunc)(struct clientparam *param, int auth) = 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
|
||||||
|
newest one on its listener after a reload (unless noforce). */
|
||||||
|
int clientauth(struct clientparam *param){
|
||||||
|
struct srvparam *orig;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if(param->version < runconf->version){
|
||||||
|
if(param->srv->noforce || !reattachfunc) param->version = runconf->version;
|
||||||
|
else if((res = (*reattachfunc)(param, 0))) return res;
|
||||||
|
}
|
||||||
|
if(!param->newsrv) return (*param->srv->authfunc)(param);
|
||||||
|
orig = param->srv;
|
||||||
|
param->srv = param->newsrv;
|
||||||
|
res = (*param->srv->authfunc)(param);
|
||||||
|
param->srv = orig;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
_3proxy_mutex_t confref_mutex;
|
_3proxy_mutex_t confref_mutex;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -641,7 +641,7 @@ for(;;){
|
|||||||
|
|
||||||
|
|
||||||
if(param->srv->needuser > 1 && !*param->username) {RETURN(4);}
|
if(param->srv->needuser > 1 && !*param->username) {RETURN(4);}
|
||||||
if((res = (*param->srv->authfunc)(param))) {
|
if((res = clientauth(param))) {
|
||||||
if (res <= 10 || haveconnection || param->transparent) RETURN(res);
|
if (res <= 10 || haveconnection || param->transparent) RETURN(res);
|
||||||
param->srv->so._closesocket(param->sostate, param->remsock);
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
||||||
param->remsock = INVALID_SOCKET;
|
param->remsock = INVALID_SOCKET;
|
||||||
@ -649,7 +649,7 @@ for(;;){
|
|||||||
param->redirtype = 0;
|
param->redirtype = 0;
|
||||||
memset(¶m->sinsl, 0, sizeof(param->sinsl));
|
memset(¶m->sinsl, 0, sizeof(param->sinsl));
|
||||||
memset(¶m->sinsr, 0, sizeof(param->sinsr));
|
memset(¶m->sinsr, 0, sizeof(param->sinsr));
|
||||||
if((res = (*param->srv->authfunc)(param))) RETURN(res);
|
if((res = clientauth(param))) RETURN(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -400,9 +400,10 @@ 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);
|
void confbegin(void);
|
||||||
int reattach(struct clientparam *param);
|
int reattach(struct clientparam *param, int auth);
|
||||||
/* reattach() where configurations are reloaded, NULL elsewhere */
|
/* reattach() where configurations are reloaded, NULL elsewhere */
|
||||||
extern int (*reattachfunc)(struct clientparam *param);
|
extern int (*reattachfunc)(struct clientparam *param, int auth);
|
||||||
|
int clientauth(struct clientparam *param);
|
||||||
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);
|
||||||
|
|||||||
@ -1653,13 +1653,13 @@ static void slotrelease(struct srvparam *srv){
|
|||||||
|
|
||||||
/* After a reload, unless noforce: a client goes by the rules of the newest
|
/* After a reload, unless noforce: a client goes by the rules of the newest
|
||||||
service on its listener, the one now accepting there, re-authenticated
|
service on its listener, the one now accepting there, re-authenticated
|
||||||
as it. It takes one of that service's maxconn slots, which counts it
|
as it if auth is set. It takes one of that service's maxconn slots,
|
||||||
there and keeps the service, and its configuration, until the client is
|
which counts it there and keeps the service, and its configuration,
|
||||||
done; a slot it held in a service before is given back. 0 to go on, or
|
until the client is done; a slot it held in a service before is given
|
||||||
the code to end the connection with: 26 if its service was removed, or
|
back. 0 to go on, or the code to end the connection with: 26 if its
|
||||||
is another one now, 27 if the newest has no slot free, or what the
|
service was removed, or is another one now, 27 if the newest has no slot
|
||||||
authentication says. */
|
free, or what the authentication says. */
|
||||||
int reattach(struct clientparam *param){
|
int reattach(struct clientparam *param, int auth){
|
||||||
struct srvparam *cur = PSRV(param), *newest, *orig;
|
struct srvparam *cur = PSRV(param), *newest, *orig;
|
||||||
int version = runconf->version, res;
|
int version = runconf->version, res;
|
||||||
|
|
||||||
@ -1689,6 +1689,7 @@ int reattach(struct clientparam *param){
|
|||||||
if(param->newsrv) slotrelease(param->newsrv);
|
if(param->newsrv) slotrelease(param->newsrv);
|
||||||
param->newsrv = newest;
|
param->newsrv = newest;
|
||||||
param->version = version;
|
param->version = version;
|
||||||
|
if(!auth) return 0;
|
||||||
/* authenticated as the newest service's client: its access lists, users,
|
/* authenticated as the newest service's client: its access lists, users,
|
||||||
limits and counters */
|
limits and counters */
|
||||||
orig = param->srv;
|
orig = param->srv;
|
||||||
|
|||||||
@ -271,7 +271,7 @@ log(logbuf);
|
|||||||
/* a reload: the client goes by the newest configuration's rules */
|
/* a reload: the client goes by the newest configuration's rules */
|
||||||
if(param->version < runconf->version){
|
if(param->version < runconf->version){
|
||||||
if(param->srv->noforce || !reattachfunc) param->version = runconf->version;
|
if(param->srv->noforce || !reattachfunc) param->version = runconf->version;
|
||||||
else if((res = (*reattachfunc)(param)) && res != 2) {RETURN(res);}
|
else if((res = (*reattachfunc)(param, 1)) && res != 2) {RETURN(res);}
|
||||||
}
|
}
|
||||||
|
|
||||||
if((param->maxtrafin64 && param->statssrv64 >= param->maxtrafin64) || (param->maxtrafout64 && param->statscli64 >= param->maxtrafout64)){
|
if((param->maxtrafin64 && param->statssrv64 >= param->maxtrafin64) || (param->maxtrafout64 && param->statscli64 >= param->maxtrafout64)){
|
||||||
|
|||||||
@ -215,6 +215,13 @@ int udpsockmap(struct clientparam *param, int timeo)
|
|||||||
if (res < 0) return 481;
|
if (res < 0) return 481;
|
||||||
if (res == 0) return 92;
|
if (res == 0) return 92;
|
||||||
|
|
||||||
|
/* a reload: the session goes by the newest configuration's rules,
|
||||||
|
as a TCP connection does in sockmap() */
|
||||||
|
if (param->version < runconf->version) {
|
||||||
|
if (param->srv->noforce || !reattachfunc) param->version = runconf->version;
|
||||||
|
else if ((len = (*reattachfunc)(param, 1)) && len != 2) return len;
|
||||||
|
}
|
||||||
|
|
||||||
/* datagram from client */
|
/* datagram from client */
|
||||||
if (p.cli >= 0 && p.fds[p.cli].revents) {
|
if (p.cli >= 0 && p.fds[p.cli].revents) {
|
||||||
unsigned char *base = param->srvbuf + UDPHDROFF;
|
unsigned char *base = param->srvbuf + UDPHDROFF;
|
||||||
|
|||||||
83
tests/cases/reattach.py
Normal file
83
tests/cases/reattach.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"""Connections of old services after a reload (unless noforce): each goes by
|
||||||
|
the rules of the newest service on its listener, taking one of its maxconn
|
||||||
|
slots, and is dropped where that cannot be."""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
origin = t.free_port()
|
||||||
|
proxy = t.free_port()
|
||||||
|
adm = t.free_port()
|
||||||
|
|
||||||
|
def config(service_line, maxconn=50):
|
||||||
|
return f"""
|
||||||
|
log
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
http data * /data
|
||||||
|
httpsrv -p{origin}
|
||||||
|
|
||||||
|
flush
|
||||||
|
maxconn {maxconn}
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
{service_line}
|
||||||
|
|
||||||
|
flush
|
||||||
|
maxconn 50
|
||||||
|
auth iponly
|
||||||
|
allow *
|
||||||
|
admin -p{adm}
|
||||||
|
"""
|
||||||
|
|
||||||
|
server = t.start("reattach", config(f"proxy -p{proxy}"), ports=[origin, proxy, adm])
|
||||||
|
|
||||||
|
def reload(conf):
|
||||||
|
before = server.output().count("Accepting connections")
|
||||||
|
t.write_config("reattach", conf)
|
||||||
|
t.http(f"http://127.0.0.1:{adm}/R")
|
||||||
|
deadline = time.time() + 10
|
||||||
|
while time.time() < deadline and server.output().count("Accepting connections") < before + 3:
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
def downloads(n):
|
||||||
|
"""n downloads of about two seconds each through the proxy, started
|
||||||
|
now; the results fill in as they finish."""
|
||||||
|
results = [{} for _ in range(n)]
|
||||||
|
|
||||||
|
def one(r):
|
||||||
|
got = t.http(f"http://127.0.0.1:{origin}/data?size=2000000&block=65536&delay=60",
|
||||||
|
proxy=f"127.0.0.1:{proxy}")
|
||||||
|
r["length"] = got.length
|
||||||
|
|
||||||
|
threads = [threading.Thread(target=one, args=(r,)) for r in results]
|
||||||
|
for th in threads:
|
||||||
|
th.start()
|
||||||
|
time.sleep(0.4)
|
||||||
|
return threads, results
|
||||||
|
|
||||||
|
# --- the newest service has one slot free: one connection moves, one is
|
||||||
|
# cut. A service holds one of its maxconn slots for the next connection
|
||||||
|
# it accepts, so maxconn 2 leaves one.
|
||||||
|
threads, results = downloads(2)
|
||||||
|
reload(config(f"proxy -p{proxy}", maxconn=2))
|
||||||
|
for th in threads:
|
||||||
|
th.join(20)
|
||||||
|
whole = sum(1 for r in results if r.get("length") == 2000000)
|
||||||
|
t.eq(1, whole, "with one slot free in the new service one download goes on")
|
||||||
|
t.contains(server.output(), " 00027 ",
|
||||||
|
"the other is cut for want of a slot (27)")
|
||||||
|
|
||||||
|
# --- the service on the port is of another type now: cut (26)
|
||||||
|
reload(config(f"proxy -p{proxy}"))
|
||||||
|
threads, results = downloads(1)
|
||||||
|
reload(config(f"socks -p{proxy}"))
|
||||||
|
for th in threads:
|
||||||
|
th.join(20)
|
||||||
|
t.ok("a proxy download is cut when a socks service takes the port") \
|
||||||
|
if results[0].get("length", 0) < 2000000 else \
|
||||||
|
t.fail("a proxy download is cut when a socks service takes the port",
|
||||||
|
"less than 2000000 bytes", results[0].get("length"))
|
||||||
|
t.contains(server.output(), " 00026 ", "as its service is another one now (26)")
|
||||||
@ -90,6 +90,19 @@ def run(t):
|
|||||||
t.eq(2000000, kept.get("length"),
|
t.eq(2000000, kept.get("length"),
|
||||||
"a download of a user the new configuration keeps finishes whole")
|
"a download of a user the new configuration keeps finishes whole")
|
||||||
|
|
||||||
|
# a connection kept alive: each request is authorized by the rules in
|
||||||
|
# force, so alice's next request after she is removed is refused
|
||||||
|
keep = t.connection("127.0.0.1", origin, proxy=proxy)
|
||||||
|
t.eq(200, t.http(url, proxy=proxy, proxy_auth=("alice", "one"), conn=keep).status,
|
||||||
|
"a kept-alive connection serves alice")
|
||||||
|
t.write_config("reload", config(origin, first, adm, "bob:CL:two"))
|
||||||
|
reload_and_wait()
|
||||||
|
r = t.http(url, proxy=proxy, proxy_auth=("alice", "one"), conn=keep)
|
||||||
|
t.eq(407, r.status, "after she is removed, her next request on it is refused "
|
||||||
|
"with a request for credentials, authorized by the new rules")
|
||||||
|
t.write_config("reload", config(origin, first, adm, "alice:CL:one bob:CL:two"))
|
||||||
|
reload_and_wait()
|
||||||
|
|
||||||
# alice is removed: her download is cut, being re-authenticated
|
# alice is removed: her download is cut, being re-authenticated
|
||||||
th, cut = during_reload("bob:CL:two", first)
|
th, cut = during_reload("bob:CL:two", first)
|
||||||
th.join(20)
|
th.join(20)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user