mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-17 08:55:49 +08:00
281 lines
7.3 KiB
Python
281 lines
7.3 KiB
Python
"""Close behaviour of a 3proxy binary on Windows, for comparing two builds.
|
|
|
|
Usage: python close_test.py path\\to\\3proxy.exe label
|
|
|
|
1. upload (#638): a client sends a file through an HTTP CONNECT tunnel and
|
|
closes as soon as the last byte is handed to its socket; the server
|
|
counts what arrives.
|
|
2. download: the server sends a file and closes; the client counts it.
|
|
3. reply while uploading: the server reads part of a large upload, answers
|
|
and closes; the client counts the answer while still sending.
|
|
4. handles: the proxy's handle count before and after many connections
|
|
which close with data still queued.
|
|
5. timing: many short sequential connections through the proxy.
|
|
"""
|
|
import socket, subprocess, sys, threading, time, os, tempfile
|
|
|
|
BIN, LABEL = sys.argv[1], sys.argv[2]
|
|
FILE = os.urandom(1580872)
|
|
|
|
|
|
def free_port():
|
|
s = socket.socket()
|
|
s.bind(("127.0.0.1", 0))
|
|
p = s.getsockname()[1]
|
|
s.close()
|
|
return p
|
|
|
|
|
|
def start_proxy():
|
|
port = free_port()
|
|
d = tempfile.mkdtemp()
|
|
cfg = os.path.join(d, "3proxy.cfg")
|
|
with open(cfg, "w") as f:
|
|
f.write("auth iponly\nallow *\nproxy -p%d\n" % port)
|
|
p = subprocess.Popen([BIN, cfg])
|
|
for _ in range(100):
|
|
try:
|
|
socket.create_connection(("127.0.0.1", port), 1).close()
|
|
break
|
|
except OSError:
|
|
time.sleep(0.05)
|
|
time.sleep(0.5)
|
|
return p, port
|
|
|
|
|
|
def tunnel(pport, dport):
|
|
c = socket.create_connection(("127.0.0.1", pport), 10)
|
|
c.sendall(("CONNECT 127.0.0.1:%d HTTP/1.1\r\nHost: 127.0.0.1:%d\r\n\r\n" % (dport, dport)).encode())
|
|
head = b""
|
|
while b"\r\n\r\n" not in head:
|
|
d = c.recv(1)
|
|
if not d:
|
|
raise OSError("tunnel refused")
|
|
head += d
|
|
if b" 200" not in head.split(b"\r\n")[0]:
|
|
raise OSError("tunnel refused: %r" % head[:40])
|
|
return c
|
|
|
|
|
|
def listener():
|
|
l = socket.socket()
|
|
l.bind(("127.0.0.1", 0))
|
|
l.listen(50)
|
|
return l, l.getsockname()[1]
|
|
|
|
|
|
def handles(pid):
|
|
try:
|
|
out = subprocess.check_output(["powershell", "-NoProfile", "-Command",
|
|
"(Get-Process -Id %d).HandleCount" % pid])
|
|
return int(out.strip())
|
|
except Exception:
|
|
return -1
|
|
|
|
|
|
def upload(pport, n=20):
|
|
full = 0
|
|
sizes = []
|
|
for _ in range(n):
|
|
l, dport = listener()
|
|
got = []
|
|
|
|
def srv():
|
|
c, _ = l.accept()
|
|
c.settimeout(15)
|
|
total = 0
|
|
try:
|
|
while True:
|
|
d = c.recv(65536)
|
|
if not d:
|
|
break
|
|
total += len(d)
|
|
except OSError:
|
|
pass
|
|
got.append(total)
|
|
c.close()
|
|
|
|
t = threading.Thread(target=srv)
|
|
t.start()
|
|
try:
|
|
c = tunnel(pport, dport)
|
|
c.sendall(FILE)
|
|
c.close()
|
|
except OSError:
|
|
pass
|
|
t.join(20)
|
|
l.close()
|
|
size = got[0] if got else -1
|
|
sizes.append(size)
|
|
full += size == len(FILE)
|
|
return "upload %d/%d complete, short ones: %s" % (
|
|
full, n, [s for s in sizes if s != len(FILE)][:6])
|
|
|
|
|
|
def download(pport, n=20):
|
|
full = 0
|
|
sizes = []
|
|
for _ in range(n):
|
|
l, dport = listener()
|
|
|
|
def srv():
|
|
c, _ = l.accept()
|
|
try:
|
|
c.sendall(FILE)
|
|
except OSError:
|
|
pass
|
|
c.close()
|
|
|
|
t = threading.Thread(target=srv)
|
|
t.start()
|
|
total = 0
|
|
try:
|
|
c = tunnel(pport, dport)
|
|
c.settimeout(15)
|
|
while True:
|
|
d = c.recv(65536)
|
|
if not d:
|
|
break
|
|
total += len(d)
|
|
c.close()
|
|
except OSError:
|
|
pass
|
|
t.join(20)
|
|
l.close()
|
|
sizes.append(total)
|
|
full += total == len(FILE)
|
|
return "download %d/%d complete, short ones: %s" % (
|
|
full, n, [s for s in sizes if s != len(FILE)][:6])
|
|
|
|
|
|
def reply_while_uploading(pport, n=10):
|
|
full = 0
|
|
errs = set()
|
|
for _ in range(n):
|
|
l, dport = listener()
|
|
reply = b"R" * 65536
|
|
|
|
def srv():
|
|
c, _ = l.accept()
|
|
got = 0
|
|
while got < 16384:
|
|
d = c.recv(16384 - got)
|
|
if not d:
|
|
break
|
|
got += len(d)
|
|
c.sendall(reply)
|
|
c.shutdown(socket.SHUT_WR)
|
|
c.settimeout(10)
|
|
try:
|
|
while c.recv(65536):
|
|
pass
|
|
except OSError:
|
|
pass
|
|
c.close()
|
|
|
|
t = threading.Thread(target=srv)
|
|
t.start()
|
|
rx = 0
|
|
try:
|
|
c = tunnel(pport, dport)
|
|
|
|
def send():
|
|
try:
|
|
c.sendall(b"x" * 1500000)
|
|
c.shutdown(socket.SHUT_WR)
|
|
except OSError as e:
|
|
errs.add("send:" + type(e).__name__)
|
|
|
|
st = threading.Thread(target=send)
|
|
st.start()
|
|
c.settimeout(15)
|
|
try:
|
|
while True:
|
|
d = c.recv(65536)
|
|
if not d:
|
|
break
|
|
rx += len(d)
|
|
except OSError as e:
|
|
errs.add("recv:" + type(e).__name__)
|
|
st.join(20)
|
|
c.close()
|
|
except OSError as e:
|
|
errs.add("tunnel:" + type(e).__name__)
|
|
t.join(20)
|
|
l.close()
|
|
full += rx == len(reply)
|
|
return "reply while uploading %d/%d complete, errors %s" % (full, n, sorted(errs) or "none")
|
|
|
|
|
|
def handle_growth(p, pport, n=300):
|
|
l, dport = listener()
|
|
stop = []
|
|
|
|
def srv():
|
|
l.settimeout(1)
|
|
while not stop:
|
|
try:
|
|
c, _ = l.accept()
|
|
except OSError:
|
|
continue
|
|
# read a little and close with the rest queued and unread
|
|
try:
|
|
c.recv(1024)
|
|
except OSError:
|
|
pass
|
|
c.close()
|
|
|
|
t = threading.Thread(target=srv)
|
|
t.start()
|
|
before = handles(p.pid)
|
|
for _ in range(n):
|
|
try:
|
|
c = tunnel(pport, dport)
|
|
c.setblocking(False)
|
|
try:
|
|
c.send(b"y" * 262144)
|
|
except OSError:
|
|
pass
|
|
c.close()
|
|
except OSError:
|
|
pass
|
|
time.sleep(8)
|
|
after = handles(p.pid)
|
|
stop.append(1)
|
|
t.join(5)
|
|
l.close()
|
|
return "handles before %d, after %d connections %d" % (before, n, after)
|
|
|
|
|
|
def timing(pport, n=300):
|
|
l, dport = listener()
|
|
|
|
def srv():
|
|
for _ in range(n):
|
|
c, _ = l.accept()
|
|
d = c.recv(16)
|
|
c.sendall(d)
|
|
c.close()
|
|
|
|
t = threading.Thread(target=srv)
|
|
t.start()
|
|
t0 = time.time()
|
|
for _ in range(n):
|
|
c = tunnel(pport, dport)
|
|
c.sendall(b"ping")
|
|
c.recv(16)
|
|
c.close()
|
|
dt = time.time() - t0
|
|
t.join(10)
|
|
l.close()
|
|
return "timing %d sequential tunnels: %.2fs (%.1f ms each)" % (n, dt, dt * 1000 / n)
|
|
|
|
|
|
p, pport = start_proxy()
|
|
try:
|
|
for f in (upload, download, reply_while_uploading, timing):
|
|
print("[%s] %s" % (LABEL, f(pport)), flush=True)
|
|
print("[%s] %s" % (LABEL, handle_growth(p, pport)), flush=True)
|
|
finally:
|
|
p.kill()
|