From ef8c102fe9f7df498626ded1f641ee455d52cd65 Mon Sep 17 00:00:00 2001 From: EuAndreh Date: Thu, 30 Apr 2026 13:20:30 -0300 Subject: Identify networks via PROXY v2 AUTHORITY; drop default network MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit papod no longer pre-creates a "default network" at boot. Instead, each accepted connection is expected to begin with a PROXY protocol v2 header (RFC: haproxy.org proxy-protocol.txt) carrying the SNI in a PP2_TYPE_AUTHORITY (0x02) TLV — exactly what untls now injects. The authority is used to look up (or create) a network by name, and the resulting network-id is bound to the client at connect time rather than at registration time. Connections that arrive without a header and without the PAPOD_NETWORK_NAME fallback set are refused with "ERROR :Closing link: (No network)". PAPOD_NETWORK_NAME exists for environments where untls is not in the path (notably the integration test harness, where binder/wscat speak raw bytes); production deployments should leave it unset and let untls supply the SNI. Unit tests cover the parser: a valid header returns the AUTHORITY and leaves trailing bytes intact; non-PROXY input returns nil and preserves the stream; PROXY without an AUTHORITY TLV returns "". --- tests/unit.clj | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'tests') diff --git a/tests/unit.clj b/tests/unit.clj index 21833e5..9eda963 100644 --- a/tests/unit.clj +++ b/tests/unit.clj @@ -1943,6 +1943,78 @@ (is (empty? (filter #(re-find #" 475 " %) replies))))))) +(def read-proxy-v2-authority! @#'papod/read-proxy-v2-authority!) + +(defn- bis-of + ^java.io.BufferedInputStream [^bytes b] + (java.io.BufferedInputStream. + (java.io.ByteArrayInputStream. b) + 4096)) + +(defn- proxy-v2-bytes + "Construct a PROXY v2 LOCAL/AF_UNSPEC header carrying a single + AUTHORITY TLV. Mirrors what untls writes to the upstream socket." + ^bytes [^String authority] + (let [auth (.getBytes authority "UTF-8") + sig (byte-array + [(unchecked-byte 0x0D) (unchecked-byte 0x0A) + (unchecked-byte 0x0D) (unchecked-byte 0x0A) + (unchecked-byte 0x00) (unchecked-byte 0x0D) + (unchecked-byte 0x0A) (unchecked-byte 0x51) + (unchecked-byte 0x55) (unchecked-byte 0x49) + (unchecked-byte 0x54) (unchecked-byte 0x0A)]) + tlv-len (+ 3 (alength auth)) + out (java.io.ByteArrayOutputStream.)] + (.write out sig) + (.write out 0x21) + (.write out 0x00) + (.write out (bit-and (bit-shift-right tlv-len 8) 0xFF)) + (.write out (bit-and tlv-len 0xFF)) + (.write out 0x02) + (.write out (bit-and (bit-shift-right (alength auth) 8) 0xFF)) + (.write out (bit-and (alength auth) 0xFF)) + (.write out auth) + (.toByteArray out))) + +(deftest test_proxy-v2-parser + (testing "valid PROXY v2 with AUTHORITY returns the SNI" + (let [hdr (proxy-v2-bytes "papo.example.com") + tail (.getBytes "NICK alice\r\n" "UTF-8") + buf (byte-array (+ (alength hdr) (alength tail)))] + (System/arraycopy hdr 0 buf 0 (alength hdr)) + (System/arraycopy tail 0 buf (alength hdr) (alength tail)) + (let [bis (bis-of buf) + authority (read-proxy-v2-authority! bis)] + (is (= authority "papo.example.com")) + ;; The trailing IRC bytes should remain readable. + (let [out (byte-array (alength tail)) + _ (.read bis out)] + (is (= (String. out "UTF-8") "NICK alice\r\n")))))) + + (testing "no PROXY signature returns nil and preserves bytes" + (let [bis (bis-of (.getBytes "NICK alice\r\n" "UTF-8"))] + (is (nil? (read-proxy-v2-authority! bis))) + (let [out (byte-array 12) + _ (.read bis out)] + (is (= (String. out "UTF-8") "NICK alice\r\n"))))) + + (testing "PROXY v2 without AUTHORITY returns empty string" + (let [sig (byte-array + [(unchecked-byte 0x0D) (unchecked-byte 0x0A) + (unchecked-byte 0x0D) (unchecked-byte 0x0A) + (unchecked-byte 0x00) (unchecked-byte 0x0D) + (unchecked-byte 0x0A) (unchecked-byte 0x51) + (unchecked-byte 0x55) (unchecked-byte 0x49) + (unchecked-byte 0x54) (unchecked-byte 0x0A)]) + out (java.io.ByteArrayOutputStream.)] + (.write out sig) + (.write out 0x21) + (.write out 0x00) + (.write out 0x00) + (.write out 0x00) + (let [bis (bis-of (.toByteArray out))] + (is (= (read-proxy-v2-authority! bis) "")))))) + (defn -main [& _args] (binding [*out* *err*] -- cgit v1.3