3proxy/tests/cases/portmap.py
Vladimir Dubrovin daa0e36e41 Cover the port mappers, tlspr, dnspr, auto and the UDP data path
tcppm and udppm forward a port each, so test both directions of each: a
request through the TCP mapper reaching its target, a datagram through the
UDP one coming back answered, and a mapper whose rules deny the client
answering nothing.

tlspr takes its destination from the name in the handshake, so point that
name at 127.0.0.1 with nsrecord and give the certificate the same name: the
name then both chooses where the request goes and is what the client checks.

dnspr answers from its cache, including the documented 0.0.0.0 record, which
is handed out as an address rather than withheld.

auto is asked to serve an HTTP proxy request, SOCKS4, SOCKS5 and a TLS
handshake on one port, and to make nothing of a request that is none of
them. Its protocols reach different places, so there are two origins.

The SOCKS UDP association was only checked for the port it binds. Send
datagrams through it as well, large and small, and check a second
association gets its own port - and that one bound inside an intport range
still relays.

A UDP service has no socket to connect to, so readiness is found by asking
until it answers rather than racing it.
2026-08-26 10:54:04 +03:00

65 lines
2.0 KiB
Python

"""The port mappers: tcppm forwards a TCP port, udppm a UDP one."""
def run(t):
# --- tcppm ---------------------------------------------------------
origin = t.free_port()
mapped = t.free_port()
refused = t.free_port()
t.start("portmap_tcp", f"""
log
auth iponly
allow *
http * /echo* echo
http * /data data
httpsrv -p{origin}
flush
auth iponly
allow *
tcppm {mapped} 127.0.0.1 {origin}
flush
auth iponly
deny *
tcppm {refused} 127.0.0.1 {origin}
""", ports=[origin, mapped, refused])
r = t.http(f"http://127.0.0.1:{mapped}/echo")
t.eq(200, r.status, "a mapped TCP port reaches the target")
t.contains(r, "path=/echo", "the target sees the request")
t.contains(r, "peer.addr=127.0.0.1", "the mapper makes the connection")
t.eq(20000, t.http(f"http://127.0.0.1:{mapped}/data?size=20000").length,
"a body passes through the mapper")
# the mapper is a service like any other, so its rules apply
r = t.http(f"http://127.0.0.1:{refused}/echo")
t.ne(200, r.status, "a mapper whose rules deny the client answers nothing")
t.stop_all()
# --- udppm ---------------------------------------------------------
# something has to be listening for the mapped datagrams to go anywhere
echo = t.udp_echo()
mapped = t.free_port()
t.start("portmap_udp", f"""
log
flush
auth iponly
allow *
udppm {mapped} 127.0.0.1 {echo}
""")
# a UDP service has no listening socket to wait for, so ask until it
# answers rather than racing it
t.wait_udp(mapped)
t.eq(b"echo:hello", t.udp_exchange(mapped, b"hello"),
"a datagram is relayed and the reply comes back")
t.eq(b"echo:second", t.udp_exchange(mapped, b"second"),
"a second datagram uses the mapping again")
big = b"x" * 2000
t.eq(b"echo:" + big, t.udp_exchange(mapped, big),
"a larger datagram survives the round trip")