aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2025-06-03 22:10:36 +0000
committerLaurent Bercot <ska@appnovation.com>2025-06-03 22:10:36 +0000
commit2b0d8dacd3b7def1d8839d725fd44ae75d71629d (patch)
tree05604bd963a1af3d41e5fff133fba118422f7a48
parent27a5ccaa6f339f7fb00dd23b62ab3e692c90e038 (diff)
downloads6-networking-2b0d8dacd3b7def1d8839d725fd44ae75d71629d.tar.gz
Add proxy-server
Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r--.gitignore1
-rw-r--r--INSTALL8
-rw-r--r--NEWS7
-rw-r--r--doc/index.html12
-rw-r--r--doc/upgrade.html13
-rw-r--r--package/deps.mak2
-rw-r--r--package/info2
-rw-r--r--package/modes1
-rw-r--r--package/targets.mak1
-rw-r--r--src/conn-tools/deps-exe/proxy-server2
-rw-r--r--src/conn-tools/proxy-server.c358
11 files changed, 396 insertions, 11 deletions
diff --git a/.gitignore b/.gitignore
index c4185f5..b02f1a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
*.lo
*.a.xyzzy
*.so.xyzzy
+/proxy-server
/s6-clockadd
/s6-clockview
/s6-getservbyname
diff --git a/INSTALL b/INSTALL
index 5cb1ebc..b9bbcf5 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,10 +6,10 @@ Build Instructions
- A POSIX-compliant C development environment
- GNU make version 3.81 or later
- - skalibs version 2.14.4.0 or later: https://skarnet.org/software/skalibs/
- - Optional (but recommended): execline version 2.9.7.0 or later: https://skarnet.org/software/execline/
- - s6 version 2.13.2.0 or later: https://skarnet.org/software/s6/
- - s6-dns version 2.4.1.0 or later: https://skarnet.org/software/s6-dns/
+ - skalibs version 2.14.5.0 or later: https://skarnet.org/software/skalibs/
+ - Optional (but recommended): execline version 2.9.7.1 or later: https://skarnet.org/software/execline/
+ - s6 version 2.13.2.1 or later: https://skarnet.org/software/s6/
+ - s6-dns version 2.4.1.1 or later: https://skarnet.org/software/s6-dns/
- Depending on whether you build the SSL tools,
bearssl version 0.6 or later: https://bearssl.org/
or libressl version 4.1.0 or later: https://libressl.org/
diff --git a/NEWS b/NEWS
index 0ec7dd4..58e5a08 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,12 @@
Changelog for s6-networking.
+In 2.7.2.0
+----------
+
+ - Support for shared libraries on MacOS
+ - New binary: proxy-server, understanding the PROXY protocol
+
+
In 2.7.1.0
----------
diff --git a/doc/index.html b/doc/index.html
index b349bb9..260213e 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -55,15 +55,15 @@ as extensions to the s6 ecosystem.
<li> A POSIX-compliant system with a standard C development environment </li>
<li> GNU make, version 3.81 or later </li>
<li> <a href="//skarnet.org/software/skalibs/">skalibs</a> version
-2.14.4.0 or later. It's a build-time requirement. It's also a run-time
+2.14.5.0 or later. It's a build-time requirement. It's also a run-time
requirement if you link against the shared version of the skalibs
library. </li>
<li> (Optional, but recommended) <a href="//skarnet.org/software/execline/">execline</a> version
-2.9.7.0 or later. It's a build-time and run-time requirement. </li>
+2.9.7.1 or later. It's a build-time and run-time requirement. </li>
<li> <a href="//skarnet.org/software/s6/">s6</a> version
-2.13.2.0 or later. It's a build-time and run-time requirement. </li>
+2.13.2.1 or later. It's a build-time and run-time requirement. </li>
<li> <a href="//skarnet.org/software/s6-dns/">s6-dns</a> version
-2.4.1.0 or later. It's a build-time requirement. It's also a run-time
+2.4.1.1 or later. It's a build-time requirement. It's also a run-time
requirement if you link against the shared version of the s6-dns
libraries. </li>
<li> If you want to build the secure communication tools:
@@ -91,8 +91,8 @@ run-time requirement if you link against its shared version. </li>
<ul>
<li> The current released version of s6-networking is
-<a href="s6-networking-2.7.1.0.tar.gz">2.7.1.0</a>.
-You can access its checksum <a href="s6-networking-2.7.1.0.tar.gz.sha256">here</a>. </li>
+<a href="s6-networking-2.7.2.0.tar.gz">2.7.2.0</a>.
+You can access its checksum <a href="s6-networking-2.7.2.0.tar.gz.sha256">here</a>. </li>
<li> Alternatively, you can checkout a copy of the
<a href="//git.skarnet.org/cgi-bin/cgit.cgi/s6-networking/">s6-networking
git repository</a>:
diff --git a/doc/upgrade.html b/doc/upgrade.html
index 2f2171b..3f438d1 100644
--- a/doc/upgrade.html
+++ b/doc/upgrade.html
@@ -18,6 +18,19 @@
<h1> What has changed in s6-networking </h1>
+<h2> in 2.7.2.0 </h2>
+
+<ul>
+ <li> <a href="//skarnet.org/software/skalibs/">skalibs</a>
+dependency bumped to 2.14.5.0 </li>
+ <li> <a href="//skarnet.org/software/execline/">execline</a>
+optional dependency bumped to 2.9.7.1 </li>
+ <li> <a href="//skarnet.org/software/s6/">s6</a>
+dependency bumped to 2.13.2.1 </li>
+ <li> <a href="//skarnet.org/software/s6-dns/">s6-dns</a>
+dependency bumped to 2.4.1.1 </li>
+</ul>
+
<h2> in 2.7.1.0 </h2>
<ul>
diff --git a/package/deps.mak b/package/deps.mak
index d75c6d8..76eb6a9 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -111,6 +111,8 @@ s6-taiclock: EXTRA_LIBS := ${SOCKET_LIB} ${SYSCLOCK_LIB}
s6-taiclock: src/clock/s6-taiclock.o -lskarnet
s6-taiclockd: EXTRA_LIBS := ${SOCKET_LIB} ${SYSCLOCK_LIB}
s6-taiclockd: src/clock/s6-taiclockd.o -lskarnet
+proxy-server: EXTRA_LIBS := ${SYSCLOCK_LIB}
+proxy-server: src/conn-tools/proxy-server.o -lskarnet
s6-getservbyname: EXTRA_LIBS := ${SOCKET_LIB}
s6-getservbyname: src/conn-tools/s6-getservbyname.o -lskarnet
s6-ident-client: EXTRA_LIBS := ${SOCKET_LIB} ${SYSCLOCK_LIB}
diff --git a/package/info b/package/info
index 73b0563..d830fec 100644
--- a/package/info
+++ b/package/info
@@ -1,4 +1,4 @@
package=s6-networking
-version=2.7.1.0
+version=2.7.2.0
category=net
package_macro_name=S6_NETWORKING
diff --git a/package/modes b/package/modes
index f403d4a..a2bfacd 100644
--- a/package/modes
+++ b/package/modes
@@ -1,3 +1,4 @@
+proxy-server 0755
s6-getservbyname 0755
s6-ident-client 0755
s6-tcpclient 0755
diff --git a/package/targets.mak b/package/targets.mak
index 26b7173..95a5971 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -1,4 +1,5 @@
BIN_TARGETS := \
+proxy-server \
s6-getservbyname \
s6-ident-client \
s6-tcpclient \
diff --git a/src/conn-tools/deps-exe/proxy-server b/src/conn-tools/deps-exe/proxy-server
new file mode 100644
index 0000000..a11a5f4
--- /dev/null
+++ b/src/conn-tools/deps-exe/proxy-server
@@ -0,0 +1,2 @@
+-lskarnet
+${SYSCLOCK_LIB}
diff --git a/src/conn-tools/proxy-server.c b/src/conn-tools/proxy-server.c
new file mode 100644
index 0000000..5a578f8
--- /dev/null
+++ b/src/conn-tools/proxy-server.c
@@ -0,0 +1,358 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <skalibs/gccattributes.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint64.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/types.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/prog.h>
+#include <skalibs/strerr.h>
+#include <skalibs/gol.h>
+#include <skalibs/tai.h>
+#include <skalibs/exec.h>
+#include <skalibs/unix-timed.h>
+
+#define NAME "proxy-server"
+#define USAGE NAME "[ -1 | -2 ] [ -v verbosity ] [ -t timeout ] prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+static unsigned int verbosity = 1 ;
+static tain deadline ;
+
+
+ /* v2 */
+
+struct v2hdr_s
+{
+ uint8_t version : 4 ;
+ uint8_t command : 4 ;
+ uint8_t family : 4 ;
+ uint8_t proto : 4 ;
+ uint16_t len ;
+} ;
+
+static inline void v2hdr_unpack (char const *s, struct v2hdr_s *h)
+{
+ h->version = s[0] >> 4 ;
+ h->command = s[0] & 0xf ;
+ h->family = s[1] >> 4 ;
+ h->proto = s[1] & 0xf ;
+ uint16_unpack_big(s+2, &h->len) ;
+}
+
+static inline void skip (uint16_t x)
+{
+ if (!x) return ;
+ char buf[x] ;
+ size_t r = timed_read_g(0, buf, x, &deadline) ;
+ if (r < x)
+ {
+ char fmtr[UINT16_FMT] ;
+ char fmtx[UINT16_FMT] ;
+ fmtr[uint16_fmt(fmtr, r)] = 0 ;
+ fmtx[uint16_fmt(fmtx, x)] = 0 ;
+ strerr_diefu4sys(111, "skip ", fmtx, " bytes: only got ", fmtr) ;
+ }
+}
+
+static void process_v2_extensions (char const *s, uint16_t len, int sub)
+{
+ while (len)
+ {
+ uint16_t n ;
+ char c ;
+ if (len < 3) strerr_dief3x(1, "invalid TLV encoding in ", sub ? "sub-" : "", "extension") ;
+ c = *s++ ;
+ uint16_unpack_big(s, &n) ; s += 2 ; len -= 3 ;
+ if (n > len) strerr_dief3x(1, "invalid TLV encoding in ", sub ? "sub-" : "", "extension") ;
+ switch (c)
+ {
+ case 0x02 : /* PP2_TYPE_AUTHORITY */
+ {
+ char tmp[n+1] ;
+ if (sub) strerr_dief1x(1, "invalid sub-extension type") ;
+ memcpy(tmp, s, n) ; tmp[n] = 0 ;
+ if (!env_mexec("SSL_TLS_SNI_SERVERNAME", tmp)) dienomem() ;
+ break ;
+ }
+ case 0x20 : /* PP2_TYPE_SSL */
+ if (n < 5) strerr_dief1x(1, "invalid sub-TLV encoding in SSL extension") ;
+ if (*s & 0x01) process_v2_extensions(s + 5, n - 5, 1) ;
+ break ;
+
+ case 0x21 : /* PP2_SUBTYPE_SSL_VERSION */
+ {
+ char tmp[n+1] ;
+ if (!sub) strerr_dief1x(1, "invalid main extension type") ;
+ memcpy(tmp, s, n) ; tmp[n] = 0 ;
+ if (!env_mexec("SSL_PROTOCOL", tmp)) dienomem() ;
+ break ;
+ }
+ case 0x22 : /* PP2_SUBTYPE_SSL_CN */
+ {
+ char tmp[n+1] ;
+ if (!sub) strerr_dief1x(1, "invalid main extension type") ;
+ memcpy(tmp, s, n) ; tmp[n] = 0 ;
+ if (!env_mexec("SSL_PEER_CERT_CN", tmp)) dienomem() ;
+ break ;
+ }
+ case 0x23 : /* PP2_SUBTYPE_SSL_CIPHER */
+ {
+ char tmp[n+1] ;
+ if (!sub) strerr_dief1x(1, "invalid main extension type") ;
+ memcpy(tmp, s, n) ; tmp[n] = 0 ;
+ if (!env_mexec("SSL_CIPHER", tmp)) dienomem() ;
+ break ;
+ }
+ default : break ;
+ }
+ s += n ; len -= n ;
+ }
+}
+
+static void do_v2 (struct v2hdr_s const *h)
+{
+ static uint16_t const famaddrlen[3] = { 12, 36, 216 } ;
+ if (h->version != 2) strerr_dief1x(1, "invalid version") ;
+ if (h->command == 0 && h->family && verbosity)
+ {
+ char fmt[UINT16_FMT] ;
+ fmt[uint16_fmt(fmt, h->family)] = 0 ;
+ strerr_warnw2x("received LOCAL command with family set to ", fmt) ;
+ }
+ else if (h->command > 1)
+ strerr_dief1x(1, "invalid command") ;
+ if (h->family > 3)
+ strerr_dief1x(1, "invalid family") ;
+ if (!h->family || !h->proto)
+ skip(h->len) ;
+ else if (h->proto > 2)
+ strerr_dief1x(1, "invalid transport protocol") ;
+ else if (h->proto == 2)
+ strerr_dief1x(2, "unsupported transport protocol: datagram") ;
+ else if (h->len < famaddrlen[h->family - 1])
+ strerr_dief1x(1, "invalid address length") ;
+ else
+ {
+ char buf[h->len] ;
+ size_t r = timed_read_g(0, buf, h->len, &deadline) ;
+ if (r < h->len) strerr_diefu1sys(111, "read address block") ;
+ switch (h->family)
+ {
+ case 1 :
+ case 2 :
+ {
+ uint16_t remoteport, localport ;
+ char remoteip[IP6_FMT] ;
+ char localip[IP6_FMT] ;
+ char remoteportfmt[UINT16_FMT] ;
+ char localportfmt[UINT16_FMT] ;
+ remoteip[h->family == 2 ? ip6_fmt(remoteip, buf) : ip4_fmt(remoteip, buf)] = 0 ;
+ localip[h->family == 2 ? ip6_fmt(localip, buf+16) : ip4_fmt(localip, buf+4)] = 0 ;
+ uint16_unpack_big(buf + 24 * h->family - 16, &remoteport) ;
+ uint16_unpack_big(buf + 24 * h->family - 14, &localport) ;
+ remoteportfmt[uint16_fmt(remoteportfmt, remoteport)] = 0 ;
+ localportfmt[uint16_fmt(localportfmt, localport)] = 0 ;
+
+ if (!env_mexec("PROTO", "TCP")
+ || !env_mexec("TCPREMOTEIP", remoteip)
+ || !env_mexec("TCPLOCALIP", localip)
+ || !env_mexec("TCPREMOTEPORT", remoteportfmt)
+ || !env_mexec("TCPLOCALPORT", localportfmt)) dienomem() ;
+ break ;
+ }
+ default :
+ {
+ char localpath[109] ;
+ strncpy(localpath, buf + 108, 108) ;
+ localpath[108] = 0 ;
+ buf[108] = 0 ;
+ if (!env_mexec("PROTO", "IPC")
+ || !env_mexec("IPCREMOTEPATH", buf)
+ || !env_mexec("IPCLOCALPATH", localpath)) dienomem() ;
+ break ;
+ }
+ }
+ if (h->len > famaddrlen[h->family-1])
+ process_v2_extensions(buf + famaddrlen[h->family-1], h->len - famaddrlen[h->family-1], 0) ;
+ }
+}
+
+static void maybe_v2 (char const *buf)
+{
+ struct v2hdr_s h ;
+ if (memcmp(buf, "\r\n\r\n\0\r\nQUIT\n", 12)) strerr_dief1x(1, "invalid magic") ;
+ v2hdr_unpack(buf + 12, &h) ;
+ do_v2(&h) ;
+}
+
+static inline void v2 (void)
+{
+ char buf[16] ;
+ size_t r = timed_read_g(0, buf, 16, &deadline) ;
+ if (r < 16)
+ {
+ if (!errno) errno = EPIPE ;
+ strerr_diefu1sys(111, "read from stdin") ;
+ }
+ maybe_v2(buf) ;
+}
+
+
+ /* v1 */
+
+static void do_v1 (char const *prebuf)
+{
+ uint16_t len = byte_chr(prebuf, 9, '\n') ;
+ if (len < 8) strerr_dief1x(1, "invalid PROXY line") ;
+ if (len == 8 && !memcmp(prebuf, "UNKNOWN\r", 8)) return ;
+
+ int is6 ;
+ char buf[102] ;
+ memcpy(buf, prebuf, 9) ; len = 9 ;
+ for (; len < 101 ; len++)
+ {
+ if (!timed_read_g(0, buf + len, 1, &deadline))
+ strerr_diefu1sys(111, "read from stdin") ;
+ if (buf[len] == '\n') break ;
+ }
+ if (len >= 101) strerr_dief1x(1, "PROXY line too long") ;
+ if (buf[len-1] != '\r') strerr_dief1x(1, "invalid PROXY line") ;
+ if (!memcmp(buf, "UNKNOWN ", 8)) return ;
+ if (memcmp(buf, "TCP", 3)) strerr_dief1x(1, "invalid protocol in PROXY line") ;
+ if (buf[3] == '6') is6 = 1 ;
+ else if (buf[3] == '4') is6 = 0 ;
+ else strerr_dief1x(1, "invalid protocol in PROXY line") ;
+
+ char remoteip[16] ;
+ char localip[16] ;
+ uint16_t remoteport, localport ;
+ uint16_t pos = 5, m ;
+ m = is6 ? ip6_scan(buf + pos, remoteip) : ip4_scan(buf + pos, remoteip) ;
+ if (!m) strerr_dief1x(1, "invalid remote ip in PROXY line") ;
+ pos += m ;
+ if (buf[pos++] != ' ') strerr_dief1x(1, "invalid PROXY line") ;
+ m = is6 ? ip6_scan(buf + pos, localip) : ip4_scan(buf + pos, localip) ;
+ if (!m) strerr_dief1x(1, "invalid local ip in PROXY line") ;
+ pos += m ;
+ if (buf[pos++] != ' ') strerr_dief1x(1, "invalid PROXY line") ;
+ m = uint16_scan(buf + pos, &remoteport) ;
+ if (!m) strerr_dief1x(1, "invalid remote port in PROXY line") ;
+ pos += m ;
+ if (buf[pos++] != ' ') strerr_dief1x(1, "invalid PROXY line") ;
+ m = uint16_scan(buf + pos, &localport) ;
+ if (!m) strerr_dief1x(1, "invalid local port in PROXY line") ;
+ pos += m ;
+ if (pos != len - 1) strerr_dief1x(1, "invalid PROXY line") ;
+
+ if (!env_mexec("PROTO", "TCP")) dienomem() ;
+ pos = 0 ;
+ m = is6 ? ip6_fmt(buf + pos, remoteip) : ip4_fmt(buf + pos, remoteip) ;
+ buf[pos + m++] = 0 ;
+ if (!env_mexec("TCPREMOTEIP", buf + pos)) dienomem() ;
+ pos += m ;
+ m = is6 ? ip6_fmt(buf + pos, localip) : ip4_fmt(buf + pos, localip) ;
+ buf[pos + m++] = 0 ;
+ if (!env_mexec("TCPLOCALIP", buf + pos)) dienomem() ;
+ pos += m ;
+ m = uint16_fmt(buf + pos, remoteport) ;
+ buf[pos + m++] = 0 ;
+ if (!env_mexec("TCPREMOTEPORT", buf + pos)) dienomem() ;
+ pos += m ;
+ m = uint16_fmt(buf + pos, localport) ;
+ buf[pos + m++] = 0 ;
+ if (!env_mexec("TCPLOCALPORT", buf + pos)) dienomem() ;
+ pos += m ;
+}
+
+static inline void v1 (void)
+{
+ char buf[15] ;
+ size_t r = timed_read_g(0, buf, 15, &deadline) ;
+ if (r < 15)
+ {
+ if (!errno) errno = EPIPE ;
+ strerr_diefu1sys(111, "read from stdin") ;
+ }
+ if (memcmp(buf, "PROXY ", 6)) strerr_dief1x(1, "invalid magic") ;
+ do_v1(buf + 6) ;
+}
+
+static void both (void)
+{
+ char buf[16] ;
+ size_t r = timed_read_g(0, buf, 15, &deadline) ;
+ if (r < 15)
+ {
+ if (!errno) errno = EPIPE ;
+ strerr_diefu1sys(111, "read from stdin") ;
+ }
+ if (!memcmp(buf, "PROXY ", 6)) do_v1(buf + 6) ;
+ else if (!timed_read_g(0, buf + 15, 1, &deadline)) strerr_diefu1sys(111, "read from stdin") ;
+ maybe_v2(buf) ;
+}
+
+enum main_golb_e
+{
+ MAIN_GOLB_V1,
+ MAIN_GOLB_V2,
+ MAIN_GOLB_N
+} ;
+
+enum main_gola_e
+{
+ MAIN_GOLA_TIMEOUT,
+ MAIN_GOLA_VERBOSITY,
+ MAIN_GOLA_N
+} ;
+
+int main (int argc, char const *const *argv)
+{
+ static gol_bool const main_golb[4] =
+ {
+ { .so = '1', .lo = "enable-v1", .set = 1, .mask = 1 << MAIN_GOLB_V1 },
+ { .so = '2', .lo = "enable-v2", .set = 1, .mask = 1 << MAIN_GOLB_V2 },
+ { .so = 0, .lo = "disable-v1", .set = 0, .mask = 1 << MAIN_GOLB_V1 },
+ { .so = 0, .lo = "disable-v2", .set = 0, .mask = 1 << MAIN_GOLB_V2 }
+ } ;
+ static gol_arg const main_gola[MAIN_GOLA_N] =
+ {
+ { .so = 't', .lo = "timeout", .i = MAIN_GOLA_TIMEOUT },
+ { .so = 'v', .lo = "verbosity", .i = MAIN_GOLA_VERBOSITY }
+ } ;
+
+ uint64_t golb = 0 ;
+ char prog_storage[PROG_pid_len(NAME)] ;
+ PROG_pid_fill(prog_storage, NAME) ;
+ PROG = prog_storage ;
+
+ {
+ char const *gola[MAIN_GOLA_N] = { 0 } ;
+ tain tto = TAIN_INFINITE_RELATIVE ;
+ unsigned int t = 0 ;
+ unsigned int golc = gol_main(argc, argv, main_golb, 4, main_gola, MAIN_GOLA_N, &golb, gola) ;
+ argc -= golc ; argv += golc ;
+ if (!argc) dieusage() ;
+ if (gola[MAIN_GOLA_TIMEOUT] && !uint0_scan(gola[MAIN_GOLA_TIMEOUT], &t))
+ strerr_dief2x(100, "timeout", " must be an unsigned integer") ;
+ if (gola[MAIN_GOLA_VERBOSITY] && !uint0_scan(gola[MAIN_GOLA_VERBOSITY], &verbosity))
+ strerr_dief2x(100, "verbosity", " must be an unsigned integer") ;
+
+ if (t) tain_from_millisecs(&tto, t) ;
+ tain_now_set_stopwatch_g() ;
+ tain_add_g(&deadline, &tto) ;
+ }
+
+ if ((golb & (1 << MAIN_GOLB_V1 | 1 << MAIN_GOLB_V2)) == 0) golb |= 1 << MAIN_GOLB_V2 ;
+ if ((golb & (1 << MAIN_GOLB_V1 | 1 << MAIN_GOLB_V2)) == 1 << MAIN_GOLB_V2) v2() ;
+ else if ((golb & (1 << MAIN_GOLB_V1 | 1 << MAIN_GOLB_V2)) == 1 << MAIN_GOLB_V1) v1() ;
+ else both() ;
+ xmexec(argv) ;
+}