You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
85 lines
3.7 KiB
85 lines
3.7 KiB
"""RFC5780-сервер стенда: два процесса, два реальных IP, по два UDP-порта.""" |
|
|
|
import argparse |
|
import ipaddress |
|
import json |
|
import select |
|
import socket |
|
import struct |
|
import zlib |
|
|
|
COOKIE = 0x2112A442 |
|
PORTS = (3478, 3479) |
|
CONTROL = 39000 |
|
|
|
|
|
def address(kind, endpoint): |
|
ip, port = endpoint |
|
mask = COOKIE if kind == 0x0020 else 0 |
|
return struct.pack("!HHBBHI", kind, 8, 0, 1, port ^ (mask >> 16), int(ipaddress.IPv4Address(ip)) ^ mask) |
|
|
|
|
|
def response(transaction, mapped, origin, other): |
|
attributes = address(0x0020, mapped) + address(0x802B, origin) + address(0x802C, other) |
|
packet = struct.pack("!HHI12s", 0x0101, len(attributes) + 8, COOKIE, transaction) + attributes |
|
return packet + struct.pack("!HHI", 0x8028, 4, zlib.crc32(packet) ^ 0x5354554E) |
|
|
|
|
|
def main(): |
|
parser = argparse.ArgumentParser(description=__doc__) |
|
parser.add_argument("--ip", required=True) |
|
parser.add_argument("--peer", required=True) |
|
args = parser.parse_args() |
|
sockets = {} |
|
for port in (*PORTS, CONTROL): |
|
endpoint = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
endpoint.bind((args.ip, port)) |
|
sockets[endpoint] = port |
|
print("READY", flush=True) |
|
try: |
|
while True: |
|
readable, _, _ = select.select(sockets, [], [], 1) |
|
for endpoint in readable: |
|
packet, client = endpoint.recvfrom(2048) |
|
port = sockets[endpoint] |
|
if port == CONTROL: |
|
if client != (args.peer, CONTROL): |
|
continue |
|
command = json.loads(packet) |
|
sender = next(sock for sock, number in sockets.items() if number == command["port"]) |
|
reply = response(bytes.fromhex(command["transaction"]), command["mapped"], |
|
(args.ip, command["port"]), command["other"]) |
|
sender.sendto(reply, tuple(command["client"])) |
|
continue |
|
if len(packet) < 20: |
|
continue |
|
kind, size, cookie, transaction = struct.unpack("!HHI12s", packet[:20]) |
|
if kind != 1 or cookie != COOKIE or size + 20 != len(packet): |
|
continue |
|
flags, offset = 0, 20 |
|
while offset + 4 <= len(packet): |
|
attribute, length = struct.unpack("!HH", packet[offset:offset + 4]) |
|
if offset + 4 + length > len(packet): |
|
raise ValueError("malformed STUN attribute") |
|
if attribute == 0x0003 and length == 4: |
|
flags = struct.unpack("!I", packet[offset + 4:offset + 8])[0] |
|
offset += 4 + ((length + 3) & ~3) |
|
other = (args.peer, PORTS[1] if port == PORTS[0] else PORTS[0]) |
|
reply_port = other[1] if flags & 2 else port |
|
if flags & 4: |
|
command = {"transaction": transaction.hex(), "mapped": client, "client": client, |
|
"port": reply_port, "other": other} |
|
control = next(sock for sock, number in sockets.items() if number == CONTROL) |
|
control.sendto(json.dumps(command).encode(), (args.peer, CONTROL)) |
|
else: |
|
sender = next(sock for sock, number in sockets.items() if number == reply_port) |
|
sender.sendto(response(transaction, client, (args.ip, reply_port), other), client) |
|
print(json.dumps({"client": client, "port": port, "change": flags, |
|
"transaction": transaction.hex()}), flush=True) |
|
finally: |
|
for endpoint in sockets: |
|
endpoint.close() |
|
|
|
|
|
if __name__ == "__main__": |
|
main()
|
|
|