mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-21 22:43:48 +00:00
The programs now use only the C++ codecs. The C stream/AEAD/AEAD-2022
implementations stay as the reference the differential tests and the C
unit tests compare against. Key derivation and the cipher tables used by
the doc snippets move to src/crypto/keys.{hpp,cpp}.
ss-manager keeps its servers in an ss_list (at most MAX_PORT_NUM,
looked up only on control messages) instead of a uthash table, and
tests/test_manager.py covers add/ping/list/remove end to end.
92 lines
4.0 KiB
Python
92 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""ss-manager end to end: add a server over the control socket, relay through
|
|
it with ss-local, see its traffic in ping, list it, and remove it."""
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
|
|
from interop import TCPHandler, TCPOrigin, free_port, peers, receive, socks
|
|
|
|
|
|
def control(port, message, timeout=5):
|
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
|
sock.settimeout(timeout)
|
|
sock.sendto(message.encode(), ("127.0.0.1", port))
|
|
return sock.recvfrom(65535)[0].decode().rstrip("\0")
|
|
|
|
|
|
def wait_listening(port, deadline=10):
|
|
end = time.monotonic() + deadline
|
|
while time.monotonic() < end:
|
|
try:
|
|
with socket.create_connection(("127.0.0.1", port), timeout=.2):
|
|
return
|
|
except OSError:
|
|
time.sleep(.05)
|
|
raise AssertionError(f"managed server did not listen on {port}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--bin", required=True, type=Path)
|
|
args = parser.parse_args()
|
|
bins = args.bin.resolve()
|
|
manager_port, server_port, local_port = free_port(), free_port(), free_port()
|
|
with TCPOrigin(("127.0.0.1", 0), TCPHandler) as origin, tempfile.TemporaryDirectory() as workdir:
|
|
threading.Thread(target=origin.serve_forever, daemon=True).start()
|
|
manager = subprocess.Popen(
|
|
[str(bins / "ss-manager"), "--manager-address", f"127.0.0.1:{manager_port}",
|
|
"--executable", str(bins / "ss-server"), "-D", workdir, "-s", "127.0.0.1",
|
|
"-m", "aes-256-gcm"],
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
try:
|
|
# The manager answers once its control socket is up.
|
|
for _ in range(50):
|
|
try:
|
|
reply = control(manager_port, "ping", timeout=.2)
|
|
break
|
|
except (TimeoutError, OSError):
|
|
time.sleep(.1)
|
|
else:
|
|
raise AssertionError("ss-manager did not answer")
|
|
assert reply.startswith("stat:"), reply
|
|
reply = control(manager_port, 'add: {"server_port": %d, "password": "secret"}' % server_port)
|
|
assert reply == "ok", reply
|
|
wait_listening(server_port)
|
|
local = [str(bins / "ss-local"), "-s", "127.0.0.1", "-p", str(server_port), "-l", str(local_port),
|
|
"-k", "secret", "-m", "aes-256-gcm"]
|
|
with peers([local], [local_port]):
|
|
sock, _ = socks(local_port, 1, ("127.0.0.1", origin.server_address[1]))
|
|
payload = os.urandom(65536)
|
|
with sock:
|
|
sock.sendall(payload)
|
|
assert receive(sock, len(payload)) == payload, "relay through the managed server failed"
|
|
# ss-server reports traffic to the manager every few seconds.
|
|
deadline = time.monotonic() + 15
|
|
traffic = 0
|
|
while time.monotonic() < deadline and traffic == 0:
|
|
stat = json.loads(control(manager_port, "ping")[len("stat: "):])
|
|
traffic = stat.get(str(server_port), 0)
|
|
time.sleep(.5)
|
|
assert traffic > 0, "no traffic statistics from the managed server"
|
|
listed = json.loads(control(manager_port, "list"))
|
|
assert any(entry.get("server_port") == str(server_port) for entry in listed), listed
|
|
assert control(manager_port, 'remove: {"server_port": %d}' % server_port) == "ok"
|
|
assert json.loads(control(manager_port, "list")) == [], "server still listed after remove"
|
|
finally:
|
|
manager.terminate()
|
|
out = manager.communicate(timeout=10)[0].decode(errors="replace")
|
|
assert manager.returncode == 0, f"ss-manager exited with {manager.returncode}:\n{out}"
|
|
print("PASS ss-manager add/ping/list/remove with traffic statistics")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|