Add a STUN server

This commit is contained in:
Fletcher Dunn
2026-05-13 14:05:35 -07:00
parent eb25f201c1
commit af470f47f6
2 changed files with 71 additions and 2 deletions
+8 -2
View File
@@ -41,13 +41,19 @@ if(ENABLE_ICE)
target_link_libraries(test_p2p GameNetworkingSockets::static_mock)
add_sanitizers(test_p2p)
# Publish the test script
# Publish the test scripts
set( P2P_TEST_SCRIPT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_p2p.py )
set( P2P_TEST_SCRIPT_SRC ${CMAKE_CURRENT_SOURCE_DIR}/test_p2p.py )
set( STUN_SERVER_SCRIPT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/stun_server.py )
set( STUN_SERVER_SCRIPT_SRC ${CMAKE_CURRENT_SOURCE_DIR}/stun_server.py )
add_custom_command(
OUTPUT ${P2P_TEST_SCRIPT}
COMMAND ${CMAKE_COMMAND} -E copy ${P2P_TEST_SCRIPT_SRC} ${P2P_TEST_SCRIPT}
DEPENDS ${P2P_TEST_SCRIPT_SRC} )
add_custom_target( publish_test_script ALL DEPENDS ${P2P_TEST_SCRIPT} )
add_custom_command(
OUTPUT ${STUN_SERVER_SCRIPT}
COMMAND ${CMAKE_COMMAND} -E copy ${STUN_SERVER_SCRIPT_SRC} ${STUN_SERVER_SCRIPT}
DEPENDS ${STUN_SERVER_SCRIPT_SRC} )
add_custom_target( publish_test_script ALL DEPENDS ${P2P_TEST_SCRIPT} ${STUN_SERVER_SCRIPT} )
add_test(NAME p2p COMMAND ${P2P_TEST_SCRIPT} WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
endif()
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""
Minimal STUN server implementing RFC 5389 Binding Request/Response.
Other STUN message types (TURN Allocate, Send/Data/Channel, etc.) are
silently ignored. Message integrity (HMAC-SHA1) is not implemented.
"""
import argparse
import socket
import struct
import sys
STUN_MAGIC_COOKIE = 0x2112A442
MSG_BINDING_REQUEST = 0x0001
MSG_BINDING_SUCCESS = 0x0101
ATTR_XOR_MAPPED_ADDRESS = 0x0020
def run(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
print("STUN server listening on %s:%d" % (host, port), flush=True)
while True:
try:
data, addr = sock.recvfrom(2048)
if len(data) < 20:
print("Dropped runt packet (%d bytes) from %s:%d" % (len(data), addr[0], addr[1]), flush=True)
continue
msg_type, _msg_len, magic = struct.unpack_from('!HHI', data)
if magic != STUN_MAGIC_COOKIE:
print("Dropped packet from %s:%d: bad magic 0x%08x" % (addr[0], addr[1], magic), flush=True)
continue
if msg_type != MSG_BINDING_REQUEST:
print("Dropped packet from %s:%d: unexpected message type 0x%04x" % (addr[0], addr[1], msg_type), flush=True)
continue
print("Binding request from %s:%d" % (addr[0], addr[1]), flush=True)
transaction_id = data[8:20]
# XOR-MAPPED-ADDRESS (IPv4)
xport = addr[1] ^ (STUN_MAGIC_COOKIE >> 16)
xip = struct.unpack('!I', socket.inet_aton(addr[0]))[0] ^ STUN_MAGIC_COOKIE
attr_body = struct.pack('!BBH I', 0x00, 0x01, xport, xip)
attr = struct.pack('!HH', ATTR_XOR_MAPPED_ADDRESS, len(attr_body)) + attr_body
response = struct.pack('!HHI', MSG_BINDING_SUCCESS, len(attr), STUN_MAGIC_COOKIE) \
+ transaction_id + attr
sock.sendto(response, addr)
except KeyboardInterrupt:
break
except Exception as e:
print("Error: %s" % e, file=sys.stderr, flush=True)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Minimal STUN server (RFC 5389 Binding Request only; no message integrity)')
parser.add_argument('--host', default='0.0.0.0',
help='IP address to bind (default: 0.0.0.0)')
parser.add_argument('--port', type=int, default=3478,
help='UDP port to listen on (default: 3478)')
args = parser.parse_args()
run(args.host, args.port)