commit 3acc8a6318968295a717ef2db8f60b4fb5cdf53d Author: Sebastian Rust Date: Wed Jan 29 12:13:15 2025 +0100 feat(dscp): initial commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3984522 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM martenseemann/quic-network-simulator-endpoint:latest + +# download and build your QUIC implementation +# [ DO WORK HERE ] +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y python3 python3-loguru +RUN ln -s /usr/bin/python3 /usr/bin/python +# copy run script and run it +COPY run_endpoint.sh . +RUN chmod +x run_endpoint.sh +COPY sender.py . +COPY server.py . +ENTRYPOINT [ "./run_endpoint.sh" ] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..884833f --- /dev/null +++ b/compose.yml @@ -0,0 +1,6 @@ +services: + dscp-toy: + image: git.rust.cloud/dscp/toy:latest + build: + context: . + dockerfile: Dockerfile diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3790bdd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "test" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "loguru>=0.7.2", +] diff --git a/run_endpoint.sh b/run_endpoint.sh new file mode 100644 index 0000000..2d73270 --- /dev/null +++ b/run_endpoint.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Set up the routing needed for the simulation +/setup.sh + +# The following variables are available for use: +# - ROLE contains the role of this execution context, client or server +# - SERVER_PARAMS contains user-supplied command line parameters +# - CLIENT_PARAMS contains user-supplied command line parameters + +if [ "$ROLE" == "client" ]; then + # Wait for the simulator to start up. + /wait-for-it.sh sim:57832 -s -t 30 + python sender.py $CLIENT_PARAMS +elif [ "$ROLE" == "server" ]; then + python server.py $SERVER_PARAMS +fi \ No newline at end of file diff --git a/sender.py b/sender.py new file mode 100644 index 0000000..4e2fbec --- /dev/null +++ b/sender.py @@ -0,0 +1,68 @@ +from argparse import ArgumentParser +from socket import socket, AF_INET, SOCK_DGRAM, IPPROTO_IP, IP_TOS +from time import sleep +import re +import time +import sys +from loguru import logger + + +def parse_bandwidth(input_str): + pattern = r'^(\d+(?:\.\d+)?)\s*([kKmMgGtT])bps$' + match = re.match(pattern, input_str) + + if match: + value = float(match.group(1)) + unit = match.group(2).lower() + + multipliers = { + 'k': 1e3, + 'm': 1e6, + 'g': 1e9, + 't': 1e12 + } + + multiplier = multipliers.get(unit, 1.0) + return value * multiplier + else: + raise ValueError(f"Invalid bandwidth: {input_str}") + + +def main(): + parser = ArgumentParser() + parser.add_argument('--host', type=str, default='localhost') + parser.add_argument('--port', type=int, default=12345) + parser.add_argument("--log-level", type=str, default="INFO") + parser.add_argument("--bandwidth", type=str, default="1Mbps") + parser.add_argument("--duration", type=int, default=10) + parser.add_argument("--dscp", type=str, default="") + args = parser.parse_args() + logger.remove() + logger.add(sys.stderr, level=args.log_level) + PACKET_SIZE = 1280 + bandwidth = parse_bandwidth(args.bandwidth) + time_between_packets = PACKET_SIZE / bandwidth + packet_cnt = 0 + logger.info(f"Connecting to {args.host}:{args.port}") + sock = socket(AF_INET, SOCK_DGRAM) + dscp = int(args.dscp) if args.dscp else 0 + if dscp: + sock.setsockopt(IPPROTO_IP, IP_TOS, dscp << 2) + CODE_WORD = "CAFEABBA" + payload = (CODE_WORD + "A" * (PACKET_SIZE - len(CODE_WORD))).encode() + t0 = time.time() + while True: + logger.debug("Sending message:") + sock.sendto(payload, (args.host, args.port)) + packet_cnt += 1 + t1 = time.time() - t0 + if t1 > args.duration: + break + time_to_sleep = time_between_packets * packet_cnt - t1 + if time_to_sleep > 0: + sleep(time_between_packets * packet_cnt - t1) + else: + logger.warning("We are too slow: {}s", -time_to_sleep) + +if __name__ == '__main__': + main() diff --git a/server.py b/server.py new file mode 100644 index 0000000..a0105b9 --- /dev/null +++ b/server.py @@ -0,0 +1,37 @@ +import socket +import argparse +import time +import sys + +def main(): + # Parse command-line arguments + parser = argparse.ArgumentParser(description="UDP Packet Receiver with Timeout") + parser.add_argument('--port', type=int, required=True, help="Port to listen for UDP packets.") + parser.add_argument('--timeout', type=int, required=True, help="Timeout in seconds to shut down if no packets are received.") + args = parser.parse_args() + + # Create a UDP socket + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(("", args.port)) + print(f"Listening for UDP packets on port {args.port}...") + + # Set the timeout for the socket + sock.settimeout(args.timeout) + + try: + while True: + try: + # Wait for a UDP packet + data, addr = sock.recvfrom(1024) # Buffer size is 1024 bytes + except socket.timeout: + print(f"No packets received for {args.timeout} seconds. Shutting down.") + break + except KeyboardInterrupt: + print("\nShutting down due to user interrupt.") + finally: + sock.close() + print("Socket closed.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..b003221 --- /dev/null +++ b/uv.lock @@ -0,0 +1,44 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "loguru" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/30/d87a423766b24db416a46e9335b9602b054a72b96a88a241f2b09b560fa8/loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac", size = 145103 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/0a/4f6fed21aa246c6b49b561ca55facacc2a44b87d65b8b92362a8e99ba202/loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb", size = 62549 }, +] + +[[package]] +name = "test" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "loguru" }, +] + +[package.metadata] +requires-dist = [{ name = "loguru", specifier = ">=0.7.2" }] + +[[package]] +name = "win32-setctime" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/dd/f95a13d2b235a28d613ba23ebad55191514550debb968b46aab99f2e3a30/win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2", size = 3676 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/e6/a7d828fef907843b2a5773ebff47fb79ac0c1c88d60c0ca9530ee941e248/win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad", size = 3604 }, +]