mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-17 08:55:49 +08:00
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>
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""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)")
|