From bb86fe1ccfaa75ab1534d66102d9f97321968e91 Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Thu, 18 Jul 2024 19:27:15 +0300 Subject: [PATCH] Add docz --- README.md | 6 ++++++ index.js | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a3bcedd..c92c79f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # valproxy UDP proxy for Valheim dedicated server Systemd socket activation. + +This is theoretically a non-Valheim specific UDP proxy that can proxy any UDP traffic from a Systemd socket +into a specified local or remote target (bi-directionally), but the feature set and configuration is focused +on running the Valheim dedicated server. + +The `systemd` folder contains example socket and server files detailing how it can be set up. diff --git a/index.js b/index.js index d183ff4..ccfdb15 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,7 @@ const SOCKET_TYPE = "udp4"; */ const SYSTEMD_READ_SOCKET_FD = 3; +/** A network target. */ class Target { /** @type {string} */ address; @@ -51,7 +52,11 @@ class Target { } } -/** @template T */ +/** + * A convenience over a 2-dimensional map. + * + * @template T + */ class TargetMap extends Map { /** * @param {Target} target @@ -89,11 +94,20 @@ class TargetMap extends Map { } } +/** + * A connection from a remote to our target. + */ class Connection { - /** @type {Target} */ + /** + * The remote client address information. + * @type {Target} + */ source; - /** @type {Socket | null} */ + /** + * The socket used to connect to the target, and to receive messages from it. + * @type {Socket | null} + */ writeSocket; /** @type {NodeJS.Timer | null} */ @@ -123,6 +137,9 @@ class Connection { this.onMessage = onMessage; } + /** + * Initialise {@link writeSocket} by setting up the event handlers and connecting it to target. + */ initTargetSocket() { this.connected = false; clearTimeout(this.reconnectTimeout); @@ -135,6 +152,7 @@ class Connection { this.source.port ); }); + this.writeSocket.on("message", (fromMsg) => { this.#resetIdleTimeout(); if (DEBUG) { @@ -149,9 +167,11 @@ class Connection { this.onMessage(fromMsg); }); + this.writeSocket.on("error", (err) => { this.#writeError(err); }); + this.writeSocket.on("close", () => { console.log( "Socket for", @@ -169,6 +189,10 @@ class Connection { } /** + * Send a message to target using our socket. + * + * If the socket is down, the message is discarded. + * * @param {Buffer} msg */ send(msg) { @@ -200,6 +224,7 @@ class Connection { this.source.port ); console.error(err); + this.writeSocket = null; this.reconnectTimeout = setTimeout(() => { this.writeSocket = createSocket(SOCKET_TYPE); @@ -255,6 +280,8 @@ READ_SOCKET.on("listening", () => { }); READ_SOCKET.on("message", (toMsg, rinfo) => { + // We need to strip off the dual stack mapped address prefix, if one exists. Otherwise our replies will not be + // delivered to the origin. let raddress = rinfo.address; if (raddress.indexOf("::ffff:") === 0) { raddress = raddress.substring(7); @@ -267,6 +294,9 @@ READ_SOCKET.on("message", (toMsg, rinfo) => { const source = new Target(raddress, rinfo.port); let connection = CONNECTIONS.get(source); + + // Create a new connection for each new client address/port pair. This allows us to route the replies from the target + // back to the correct client address/port and thus the correct remote client. if (!connection) { clearTimeout(TOTAL_IDLE_TIMER); @@ -305,6 +335,8 @@ READ_SOCKET.on("close", () => { console.log("Systemd read socket closed."); }); +// Bind the read socket to the file descriptor given by Systemd. Systemd is already listening to packets for us, so we +// don't open our own socket. READ_SOCKET.bind({ fd: SYSTEMD_READ_SOCKET_FD, });