mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-16 00:25:48 +08:00
-s[N]:MIN,STREAK,SMALL sets where the automatic mode splices, as -g does for grace delay: a direction is spliced after STREAK reads in a row of MIN bytes or more (SMALL from a TLS socket), copied again after more than STREAK splices in a row of less than SMALL, and a body of known length is spliced if MIN or more of it is left. The defaults, 16384,2,4096, are what was built in; a value left out or 0 keeps its default. The man page describes it, and its HTML copy, which still said splice() was not built by default, now has the current text. A test case relays answers of every size, chunked ones and a 3 MB upload through every mode, two of them with heuristics set, and compares them byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
"""splice(): every mode relays what it is given unchanged, small and large,
|
|
both ways, the automatic mode's heuristics set or not (-s[N]:MIN,STREAK,SMALL).
|
|
|
|
Where splice() is not built (anything but Linux) -s is accepted and ignored,
|
|
so the same checks then cover copying.
|
|
"""
|
|
|
|
import os
|
|
import socket
|
|
import threading
|
|
|
|
MODES = ["-s0", "-s", "-s3", "-s:65536,3,8192", "-s2:32768,2,4096"]
|
|
|
|
|
|
def echo_origin(listener):
|
|
"""Sends back whatever each connection sends, until it shuts down its
|
|
side."""
|
|
def serve(c):
|
|
try:
|
|
while True:
|
|
b = c.recv(65536)
|
|
if not b:
|
|
break
|
|
c.sendall(b)
|
|
c.shutdown(socket.SHUT_WR)
|
|
except OSError:
|
|
pass
|
|
c.close()
|
|
|
|
while True:
|
|
try:
|
|
c, _ = listener.accept()
|
|
except OSError:
|
|
return
|
|
threading.Thread(target=serve, args=(c,), daemon=True).start()
|
|
|
|
|
|
def echo_through(port, data):
|
|
"""data sent through the mapped port while what comes back is read; the
|
|
sending side is shut down once all is sent."""
|
|
s = socket.create_connection(("127.0.0.1", port), timeout=20)
|
|
got = []
|
|
|
|
def reader():
|
|
while True:
|
|
try:
|
|
b = s.recv(65536)
|
|
except OSError:
|
|
return
|
|
if not b:
|
|
return
|
|
got.append(b)
|
|
|
|
th = threading.Thread(target=reader, daemon=True)
|
|
th.start()
|
|
s.sendall(data)
|
|
s.shutdown(socket.SHUT_WR)
|
|
th.join(30)
|
|
s.close()
|
|
return b"".join(got)
|
|
|
|
|
|
def run(t):
|
|
origin = t.free_port()
|
|
listener = socket.socket()
|
|
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
listener.bind(("127.0.0.1", 0))
|
|
listener.listen(64)
|
|
echo = listener.getsockname()[1]
|
|
threading.Thread(target=echo_origin, args=(listener,), daemon=True).start()
|
|
|
|
proxies, mapped = [], []
|
|
conf = f"""
|
|
log
|
|
auth iponly
|
|
allow *
|
|
http data * /data
|
|
httpsrv -p{origin}
|
|
|
|
flush
|
|
auth iponly
|
|
allow *
|
|
"""
|
|
for mode in MODES:
|
|
p, m = t.free_port(), t.free_port()
|
|
proxies.append(p)
|
|
mapped.append(m)
|
|
conf += f" proxy {mode} -p{p}\n tcppm {mode} {m} 127.0.0.1 {echo}\n"
|
|
t.start("splice", conf, ports=[origin] + proxies + mapped)
|
|
|
|
direct = {size: t.http(f"http://127.0.0.1:{origin}/data?size={size}").body
|
|
for size in (1000, 100000, 5000000)}
|
|
chunked = t.http(f"http://127.0.0.1:{origin}/data?size=1000000&chunked=1").body
|
|
up = os.urandom(3000000)
|
|
|
|
try:
|
|
for mode, p, m in zip(MODES, proxies, mapped):
|
|
proxy = f"127.0.0.1:{p}"
|
|
for size, body in direct.items():
|
|
got = t.http(f"http://127.0.0.1:{origin}/data?size={size}", proxy=proxy)
|
|
if got.body == body:
|
|
t.ok(f"{mode}: a {size} byte answer arrives unchanged")
|
|
else:
|
|
t.fail(f"{mode}: a {size} byte answer arrives unchanged",
|
|
f"{size} bytes", f"{got.length} different bytes")
|
|
got = t.http(f"http://127.0.0.1:{origin}/data?size=1000000&chunked=1", proxy=proxy)
|
|
if got.body == chunked:
|
|
t.ok(f"{mode}: a chunked answer arrives unchanged")
|
|
else:
|
|
t.fail(f"{mode}: a chunked answer arrives unchanged",
|
|
f"{len(chunked)} bytes", f"{got.length} different bytes")
|
|
back = echo_through(m, up)
|
|
if back == up:
|
|
t.ok(f"{mode}: 3 MB sent through tcppm come back unchanged")
|
|
else:
|
|
t.fail(f"{mode}: 3 MB sent through tcppm come back unchanged",
|
|
f"{len(up)} bytes", f"{len(back)} bytes, different")
|
|
small = echo_through(m, b"hello")
|
|
t.eq(b"hello", small, f"{mode}: a few bytes through tcppm come back")
|
|
finally:
|
|
listener.close()
|