From e18914cf57e3fc2350c3d917d8cc38de730f6822 Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Tue, 16 Dec 2025 17:06:05 -0800 Subject: [PATCH 01/22] add basic unbounded censored client outbound --- constant/proxy.go | 9 +- go.mod | 36 ++++-- go.sum | 70 +++++++++++ option/unbounded.go | 35 ++++++ protocol/register.go | 2 + protocol/unbounded/outbound.go | 205 +++++++++++++++++++++++++++++++++ 6 files changed, 343 insertions(+), 14 deletions(-) create mode 100644 option/unbounded.go create mode 100644 protocol/unbounded/outbound.go diff --git a/constant/proxy.go b/constant/proxy.go index ff25e24..80f346f 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -1,10 +1,11 @@ package constant const ( - TypeAmnezia = "amnezia" - TypeALGeneva = "algeneva" - TypeOutline = "outline" - TypeWATER = "water" + TypeAmnezia = "amnezia" + TypeALGeneva = "algeneva" + TypeOutline = "outline" + TypeWATER = "water" + TypeUnbounded = "unbounded" ) const ( diff --git a/go.mod b/go.mod index 7b2096e..b453fa4 100644 --- a/go.mod +++ b/go.mod @@ -40,20 +40,36 @@ require ( require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect + github.com/enobufs/go-nats v0.0.1 // indirect + github.com/getlantern/broflake v0.0.0-20251207191201-44af47ca931f // indirect github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect github.com/getlantern/keepcurrent v0.0.0-20221014183517-fcee77376b89 // indirect github.com/go-stack/stack v1.8.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect github.com/nwaples/rardecode v1.1.2 // indirect + github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/oschwald/geoip2-golang v1.9.0 // indirect github.com/oschwald/maxminddb-golang v1.13.1 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/pion/dtls/v3 v3.0.6 // indirect + github.com/pion/ice/v4 v4.0.10 // indirect + github.com/pion/mdns/v2 v2.0.7 // indirect + github.com/pion/srtp/v3 v3.0.6 // indirect + github.com/pion/stun/v3 v3.0.0 // indirect + github.com/pion/transport/v3 v3.0.7 // indirect + github.com/pion/turn v1.3.7 // indirect + github.com/pion/turn/v4 v4.0.2 // indirect + github.com/pion/webrtc/v4 v4.1.2 // indirect + github.com/quic-go/quic-go v0.51.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect + github.com/wlynxg/anet v0.0.5 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect @@ -155,21 +171,21 @@ require ( github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pion/datachannel v1.5.2 // indirect - github.com/pion/dtls/v2 v2.2.7 // indirect + github.com/pion/datachannel v1.5.10 // indirect + github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/ice/v2 v2.2.6 // indirect - github.com/pion/interceptor v0.1.11 // indirect - github.com/pion/logging v0.2.2 // indirect + github.com/pion/interceptor v0.1.40 // indirect + github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns v0.0.5 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/rtcp v1.2.9 // indirect - github.com/pion/rtp v1.7.13 // indirect - github.com/pion/sctp v1.8.8 // indirect - github.com/pion/sdp/v3 v3.0.5 // indirect + github.com/pion/rtcp v1.2.15 // indirect + github.com/pion/rtp v1.8.19 // indirect + github.com/pion/sctp v1.8.39 // indirect + github.com/pion/sdp/v3 v3.0.14 // indirect github.com/pion/srtp/v2 v2.0.9 // indirect github.com/pion/stun v0.6.1 // indirect - github.com/pion/transport v0.13.1 // indirect - github.com/pion/transport/v2 v2.2.3 // indirect + github.com/pion/transport v0.14.1 // indirect + github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/turn/v2 v2.0.8 // indirect github.com/pion/webrtc/v3 v3.1.42 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 1566318..1550907 100644 --- a/go.sum +++ b/go.sum @@ -150,6 +150,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/enobufs/go-nats v0.0.1 h1:uzC0mxan4hyGzUFG7cShFmk6c+XYgfoT8yTBgF5CJYw= +github.com/enobufs/go-nats v0.0.1/go.mod h1:ZF0vpSk02ALIMFsHkIO4MHXUN1v3nLZssTaG+fgX/io= github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= @@ -165,6 +167,8 @@ github.com/gaukas/wazerofs v0.1.0 h1:wIkW1bAxSnpaaVkQ5LOb1tm1BXdVap3eKjJpVWIqt2E github.com/gaukas/wazerofs v0.1.0/go.mod h1:+JECB9Fwt0taPqSgHckG9lmT3tcoVK+9VJozTsq9UlI= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 h1:w2/RqYPw7PbTYfUMS2aToD5DMKLBnQed+fkTEYTKAqQ= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52/go.mod h1:PrNR8tMXO26YNs8K9653XCUH7u2Kv4OdfFC3Ke1GsX0= +github.com/getlantern/broflake v0.0.0-20251207191201-44af47ca931f h1:qgLbvUgoI357AfWpxA1DBSG0nX5XQ8VN5X44v0yscNM= +github.com/getlantern/broflake v0.0.0-20251207191201-44af47ca931f/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= @@ -229,6 +233,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -284,6 +290,8 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -402,7 +410,10 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -421,48 +432,93 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= +github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= +github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= +github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= +github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= +github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig= github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE= +github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= +github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs= github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= +github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= +github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= +github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= +github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= +github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= +github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= +github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= +github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= +github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/sctp v1.8.8 h1:5EdnnKI4gpyR1a1TwbiS/wxEgcUWBHsc7ILAjARJB+U= github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs= +github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= +github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU= github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= +github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= +github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ= github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4= +github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= +github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= +github.com/pion/stun v0.3.1/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= +github.com/pion/stun v0.3.2/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= +github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= +github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/transport v0.8.6/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno= +github.com/pion/transport v0.8.8/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8= github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA= github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg= +github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= +github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= +github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pion/turn v1.3.5/go.mod h1:zGPB7YYB/HTE9MWn0Sbznz8NtyfeVeanZ834cG/MXu0= +github.com/pion/turn v1.3.7 h1:/nyM2XrlZILD7KKfnh0oYEBTRG5JlbH21ibjluRoCeo= +github.com/pion/turn v1.3.7/go.mod h1:js0LBFqMcKAlaWAXoYqNjefGI7kfJCrkCBfHGuTToXE= github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= +github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= +github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA= github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA= +github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= +github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -491,6 +547,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc= +github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/refraction-networking/water v0.7.1-alpha h1:Q7AVr9qx7vUNhJYK1F96DIweDPZ4e5IdnRN/OpHhGUo= github.com/refraction-networking/water v0.7.1-alpha/go.mod h1:/Es8MEj+895tQhx6Sl09It+Hmk7eC4tuPbxSvgsBd2c= @@ -617,6 +675,9 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= @@ -684,6 +745,7 @@ golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -728,11 +790,13 @@ golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -783,23 +847,27 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -807,10 +875,12 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= diff --git a/option/unbounded.go b/option/unbounded.go new file mode 100644 index 0000000..10b4a6e --- /dev/null +++ b/option/unbounded.go @@ -0,0 +1,35 @@ +package option + +import ( + "github.com/sagernet/sing-box/option" +) + +// Note that values which map to time.Duration in Unbounded's options structs are +// represented here as int (which will be converted to seconds). This means you +// can't set a time.Duration of 0, because we can't disambiguate it from an unset +// value. You also can't set any of the other int types to 0. But you probably +// shouldn't be setting any of this stuff to 0! +type UnboundedOutboundOptions struct { + option.DialerOptions + option.ServerOptions + // BroflakeOptions + CTableSize int `json:"c_table_size,omitempty"` + PTableSize int `json:"p_table_size,omitempty"` + BusBufferSz int `json:"bus_buffer_sz,omitempty"` + Netstated string `json:"netstated,omitempty"` + // WebRTCOptions + DiscoverySrv string `json:"discovery_srv,omitempty"` + DiscoveryEndpoint string `json:"discovery_endpoint,omitempty"` + GenesisAddr string `json:"genesis_addr,omitempty"` + NATFailTimeout int `json:"nat_fail_timeout,omitempty"` + STUNBatchSize int `json:"stun_batch_size,omitempty"` + Tag string `json:"tag,omitempty"` + Patience int `json:"patience,omitempty"` + ErrorBackoff int `json:"error_backoff,omitempty"` + ConsumerSessionID string `json:"consumer_session_id,omitempty"` + // EgressOptions + EgressAddr string `json:"egress_addr,omitempty"` + EgressEndpoint string `json:"egress_endpoint,omitempty"` + EgressConnectTimeout int `json:"egress_connect_timeout,omitempty"` + EgressErrorBackoff int `json:"egress_error_backoff,omitempty"` +} diff --git a/protocol/register.go b/protocol/register.go index 0489bca..b1b22aa 100644 --- a/protocol/register.go +++ b/protocol/register.go @@ -11,6 +11,7 @@ import ( "github.com/getlantern/lantern-box/protocol/amnezia" "github.com/getlantern/lantern-box/protocol/group" + "github.com/getlantern/lantern-box/protocol/unbounded" "github.com/getlantern/lantern-box/protocol/water" "github.com/getlantern/lantern-box/protocol/algeneva" @@ -72,6 +73,7 @@ func registerOutbounds(registry *outbound.Registry) { algeneva.RegisterOutbound(registry) outline.RegisterOutbound(registry) water.RegisterOutbound(registry) + unbounded.RegisterOutbound(registry) // utility outbounds group.RegisterFallback(registry) diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go new file mode 100644 index 0000000..b9bd604 --- /dev/null +++ b/protocol/unbounded/outbound.go @@ -0,0 +1,205 @@ +package unbounded + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "log" + "math/big" + "net" + "os" + "time" + + UBClientcore "github.com/getlantern/broflake/clientcore" + UBCommon "github.com/getlantern/broflake/common" + C "github.com/getlantern/lantern-box/constant" + "github.com/getlantern/lantern-box/option" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + singlog "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +// WIP usage: edit sing-box/include/registry.go to import and register this protocol + +type logAdapter struct { + singBoxLogger singlog.ContextLogger +} + +func (l logAdapter) Write(p []byte) (int, error) { + l.singBoxLogger.Info(string(p)) + return len(p), nil +} + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.UnboundedOutboundOptions](registry, C.TypeUnbounded, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger + broflakeConn *UBClientcore.BroflakeConn + dial UBClientcore.SOCKS5Dialer +} + +func NewOutbound( + ctx context.Context, + router adapter.Router, + logger singlog.ContextLogger, + tag string, + options option.UnboundedOutboundOptions, +) (adapter.Outbound, error) { + bfOpt := UBClientcore.NewDefaultBroflakeOptions() + if options.CTableSize != 0 { + bfOpt.CTableSize = options.CTableSize + } + + if options.PTableSize != 0 { + bfOpt.PTableSize = options.PTableSize + } + + if options.BusBufferSz != 0 { + bfOpt.BusBufferSz = options.BusBufferSz + } + + if options.Netstated != "" { + bfOpt.Netstated = options.Netstated + } + + rtcOpt := UBClientcore.NewDefaultWebRTCOptions() + if options.DiscoverySrv != "" { + rtcOpt.DiscoverySrv = options.DiscoverySrv + } + + if options.DiscoveryEndpoint != "" { + rtcOpt.Endpoint = options.DiscoveryEndpoint + } + + if options.GenesisAddr != "" { + rtcOpt.GenesisAddr = options.GenesisAddr + } + + if options.NATFailTimeout != 0 { + rtcOpt.NATFailTimeout = time.Duration(options.NATFailTimeout) * time.Second + } + + if options.STUNBatchSize != 0 { + rtcOpt.STUNBatchSize = uint32(options.STUNBatchSize) + } + + if options.Tag != "" { + rtcOpt.Tag = options.Tag + } + + if options.Patience != 0 { + rtcOpt.Patience = time.Duration(options.Patience) * time.Second + } + + if options.ErrorBackoff != 0 { + rtcOpt.ErrorBackoff = time.Duration(options.ErrorBackoff) * time.Second + } + + if options.ConsumerSessionID != "" { + rtcOpt.ConsumerSessionID = options.ConsumerSessionID + } + + // XXX: This sing-box outbound implements a "desktop" type Unbounded peer, and + // desktop peers don't connect to the egress server, so these egress settings + // have no effect. We plumb them through here for the sake of future extensibility. + egOpt := UBClientcore.NewDefaultEgressOptions() + if options.EgressAddr != "" { + egOpt.Addr = options.EgressAddr + } + + if options.EgressEndpoint != "" { + egOpt.Endpoint = options.EgressEndpoint + } + + if options.EgressConnectTimeout != 0 { + egOpt.ConnectTimeout = time.Duration(options.EgressConnectTimeout) * time.Second + } + + if options.EgressErrorBackoff != 0 { + egOpt.ErrorBackoff = time.Duration(options.EgressErrorBackoff) * time.Second + } + + la := logAdapter{ + singBoxLogger: logger, + } + + UBCommon.SetDebugLogger(log.New(la, "", 0)) + + BFConn, _, err := UBClientcore.NewBroflake(bfOpt, rtcOpt, egOpt) + if err != nil { + return nil, err + } + + // TODO: plumb through a real TLS cert and get rid of the self-signed generator? + QUICLayer, err := UBClientcore.NewQUICLayer(BFConn, generateSelfSignedTLSConfig()) + if err != nil { + return nil, err + } + + dialer := UBClientcore.CreateSOCKS5Dialer(QUICLayer) + + o := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions( + C.TypeUnbounded, + tag, + []string{N.NetworkTCP}, // XXX: Unbounded only supports TCP (not UDP) for now + options.DialerOptions, + ), + logger: logger, + broflakeConn: BFConn, + dial: dialer, + } + + go QUICLayer.ListenAndMaintainQUICConnection() + return o, nil +} + +func (h *Outbound) DialContext( + ctx context.Context, + network string, + destination M.Socksaddr, +) (net.Conn, error) { + // XXX: this is the log pattern for N.NetworkTCP + h.logger.InfoContext(ctx, "outbound connection to ", destination) + + // XXX: network is ignored by Unbounded's SOCKS5 dialer + return h.dial(ctx, network, destination.String()) +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +// TODO: delete me +func generateSelfSignedTLSConfig() *tls.Config { + key, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + panic(err) + } + + template := x509.Certificate{SerialNumber: big.NewInt(1)} + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + if err != nil { + panic(err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + panic(err) + } + return &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + NextProtos: []string{"broflake"}, + } +} From 192a3d6085183e80e8c8eb8d2ac51c84beb30074 Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Wed, 31 Dec 2025 14:04:37 -0800 Subject: [PATCH 02/22] plumb through STUNBatch function from the outbound options --- option/unbounded.go | 1 + protocol/unbounded/outbound.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/option/unbounded.go b/option/unbounded.go index 10b4a6e..2338943 100644 --- a/option/unbounded.go +++ b/option/unbounded.go @@ -23,6 +23,7 @@ type UnboundedOutboundOptions struct { GenesisAddr string `json:"genesis_addr,omitempty"` NATFailTimeout int `json:"nat_fail_timeout,omitempty"` STUNBatchSize int `json:"stun_batch_size,omitempty"` + STUNBatch func(size uint32) (batch []string, err error) Tag string `json:"tag,omitempty"` Patience int `json:"patience,omitempty"` ErrorBackoff int `json:"error_backoff,omitempty"` diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index b9bd604..f10e733 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -92,6 +92,10 @@ func NewOutbound( rtcOpt.STUNBatchSize = uint32(options.STUNBatchSize) } + if options.STUNBatch != nil { + rtcOpt.STUNBatch = options.STUNBatch + } + if options.Tag != "" { rtcOpt.Tag = options.Tag } From 95bbf6455d2cd9882ca440322ca5b43aa4fe0a75 Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Sun, 4 Jan 2026 12:29:12 -0800 Subject: [PATCH 03/22] implement the outbound close function --- protocol/unbounded/outbound.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index f10e733..07e84f3 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -45,6 +45,8 @@ type Outbound struct { logger logger.ContextLogger broflakeConn *UBClientcore.BroflakeConn dial UBClientcore.SOCKS5Dialer + ui UBClientcore.UI + ql *UBClientcore.QUICLayer } func NewOutbound( @@ -138,7 +140,7 @@ func NewOutbound( UBCommon.SetDebugLogger(log.New(la, "", 0)) - BFConn, _, err := UBClientcore.NewBroflake(bfOpt, rtcOpt, egOpt) + BFConn, ui, err := UBClientcore.NewBroflake(bfOpt, rtcOpt, egOpt) if err != nil { return nil, err } @@ -161,6 +163,8 @@ func NewOutbound( logger: logger, broflakeConn: BFConn, dial: dialer, + ui: ui, + ql: QUICLayer, } go QUICLayer.ListenAndMaintainQUICConnection() @@ -183,6 +187,12 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return nil, os.ErrInvalid } +func (h *Outbound) Close() error { + h.ql.Close() + h.ui.Stop() + return nil +} + // TODO: delete me func generateSelfSignedTLSConfig() *tls.Config { key, err := rsa.GenerateKey(rand.Reader, 1024) From e3b7f88729b85a83e801972b1d8be4d7fb7db5c8 Mon Sep 17 00:00:00 2001 From: garmr Date: Tue, 27 Jan 2026 16:33:38 -0800 Subject: [PATCH 04/22] implement wrapper for webrtc net interface --- go.mod | 4 +- go.sum | 19 ++----- protocol/unbounded/net.go | 101 +++++++++++++++++++++++++++++++++ protocol/unbounded/outbound.go | 17 +++++- 4 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 protocol/unbounded/net.go diff --git a/go.mod b/go.mod index b453fa4..f19a612 100644 --- a/go.mod +++ b/go.mod @@ -22,8 +22,10 @@ require ( github.com/Jigsaw-Code/outline-sdk v0.0.19 github.com/Jigsaw-Code/outline-sdk/x v0.0.2 github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 + github.com/getlantern/broflake v0.0.0-20260128002846-de79df6a2853 github.com/getlantern/lantern-water v0.0.0-20250331153903-07abebe611e8 github.com/gobwas/ws v1.4.0 + github.com/pion/transport/v3 v3.0.7 github.com/refraction-networking/water v0.7.1-alpha github.com/sagernet/sing v0.7.13 github.com/sagernet/sing-box v1.11.5 @@ -41,7 +43,6 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/enobufs/go-nats v0.0.1 // indirect - github.com/getlantern/broflake v0.0.0-20251207191201-44af47ca931f // indirect github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect @@ -63,7 +64,6 @@ require ( github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/srtp/v3 v3.0.6 // indirect github.com/pion/stun/v3 v3.0.0 // indirect - github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/turn v1.3.7 // indirect github.com/pion/turn/v4 v4.0.2 // indirect github.com/pion/webrtc/v4 v4.1.2 // indirect diff --git a/go.sum b/go.sum index 1550907..998e95f 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/gaukas/wazerofs v0.1.0 h1:wIkW1bAxSnpaaVkQ5LOb1tm1BXdVap3eKjJpVWIqt2E github.com/gaukas/wazerofs v0.1.0/go.mod h1:+JECB9Fwt0taPqSgHckG9lmT3tcoVK+9VJozTsq9UlI= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 h1:w2/RqYPw7PbTYfUMS2aToD5DMKLBnQed+fkTEYTKAqQ= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52/go.mod h1:PrNR8tMXO26YNs8K9653XCUH7u2Kv4OdfFC3Ke1GsX0= -github.com/getlantern/broflake v0.0.0-20251207191201-44af47ca931f h1:qgLbvUgoI357AfWpxA1DBSG0nX5XQ8VN5X44v0yscNM= -github.com/getlantern/broflake v0.0.0-20251207191201-44af47ca931f/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= +github.com/getlantern/broflake v0.0.0-20260128002846-de79df6a2853 h1:cF6ky5h335dZp+2Ae94mwnOdAfLTq1X5kkNPDIZi93k= +github.com/getlantern/broflake v0.0.0-20260128002846-de79df6a2853/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= @@ -410,7 +410,6 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= @@ -418,6 +417,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= @@ -430,13 +431,11 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= @@ -446,12 +445,10 @@ github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig= github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= -github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs= github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= @@ -461,21 +458,16 @@ github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= -github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= -github.com/pion/sctp v1.8.8 h1:5EdnnKI4gpyR1a1TwbiS/wxEgcUWBHsc7ILAjARJB+U= -github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs= github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU= github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= @@ -495,13 +487,10 @@ github.com/pion/transport v0.8.8/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83Wv github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= -github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA= github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg= github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= -github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA= -github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= diff --git a/protocol/unbounded/net.go b/protocol/unbounded/net.go new file mode 100644 index 0000000..20d994b --- /dev/null +++ b/protocol/unbounded/net.go @@ -0,0 +1,101 @@ +package unbounded + +import ( + "context" + "net" + + "github.com/pion/transport/v3" + "github.com/pion/transport/v3/stdnet" + + "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ transport.Net = (*rtcNet)(nil) + +// rtcNet is a [transport.Net] implementation that uses a N.Dialer for dialing. +// It wraps the base [stdnet.Net] and overrides the Dial and ListenPacket methods +// to use the DefaultDialer. +type rtcNet struct { + *stdnet.Net + ctx context.Context + dialer N.Dialer +} + +func newRTCNet(ctx context.Context, dialer N.Dialer) (*rtcNet, error) { + snet, err := stdnet.NewNet() + if err != nil { + return nil, err + } + return &rtcNet{ + Net: snet, + ctx: ctx, + dialer: dialer, + }, nil +} + +func (n *rtcNet) Dial(network string, address string) (net.Conn, error) { + destination := metadata.ParseSocksaddr(address) + return n.dialer.DialContext(n.ctx, network, destination) +} + +func (n *rtcNet) ListenPacket(network string, address string) (net.PacketConn, error) { + destination := metadata.ParseSocksaddr(address) + return n.dialer.ListenPacket(n.ctx, destination) +} + +func (n *rtcNet) DialUDP(network string, laddr *net.UDPAddr, raddr *net.UDPAddr) (transport.UDPConn, error) { + destination := metadata.SocksaddrFromNet(raddr) + conn, err := n.dialer.DialContext(n.ctx, "udp", destination) + if err != nil { + return nil, err + } + // the DefaultDialer should always return a net.UDPConn + if udpConn, ok := conn.(transport.UDPConn); ok { + return udpConn, nil + } + conn.Close() + return nil, &net.OpError{ + Op: "dial", + Net: network, + Addr: raddr, + Err: net.InvalidAddrError("not a UDP connection"), + } +} + +func (n *rtcNet) DialTCP(network string, laddr *net.TCPAddr, raddr *net.TCPAddr) (transport.TCPConn, error) { + destination := metadata.SocksaddrFromNet(raddr) + conn, err := n.dialer.DialContext(n.ctx, "tcp", destination) + if err != nil { + return nil, err + } + // the DefaultDialer should always return a net.TCPConn + if tcpConn, ok := conn.(transport.TCPConn); ok { + return tcpConn, nil + } + conn.Close() + return nil, &net.OpError{ + Op: "dial", + Net: network, + Addr: raddr, + Err: net.InvalidAddrError("not a TCP connection"), + } +} + +// maybe well implement these later. might be useful +// func (n *rtcNet) ResolveIPAddr(network string, address string) (*net.IPAddr, error) {} +// func (n *rtcNet) ResolveUDPAddr(network string, address string) (*net.UDPAddr, error) {} +// func (n *rtcNet) ResolveTCPAddr(network string, address string) (*net.TCPAddr, error) {} + +func (n *rtcNet) CreateDialer(dialer *net.Dialer) transport.Dialer { + return &rtcDialer{n.ctx, n.dialer} +} + +type rtcDialer struct { + ctx context.Context + dialer N.Dialer +} + +func (d *rtcDialer) Dial(network, address string) (net.Conn, error) { + return d.dialer.DialContext(d.ctx, network, metadata.ParseSocksaddr(address)) +} diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 07e84f3..50cde25 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -15,14 +15,16 @@ import ( UBClientcore "github.com/getlantern/broflake/clientcore" UBCommon "github.com/getlantern/broflake/common" - C "github.com/getlantern/lantern-box/constant" - "github.com/getlantern/lantern-box/option" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" singlog "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + + C "github.com/getlantern/lantern-box/constant" + "github.com/getlantern/lantern-box/option" ) // WIP usage: edit sing-box/include/registry.go to import and register this protocol @@ -140,6 +142,17 @@ func NewOutbound( UBCommon.SetDebugLogger(log.New(la, "", 0)) + // wrap sing-box dialer in transport.Net for pion/webrtc usage + outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) + if err != nil { + return nil, err + } + rtcNet, err := newRTCNet(ctx, outboundDialer) + if err != nil { + return nil, err + } + rtcOpt.Net = rtcNet + BFConn, ui, err := UBClientcore.NewBroflake(bfOpt, rtcOpt, egOpt) if err != nil { return nil, err From 7260330e7ec536e4f7cef3354a0b3e6f9d670816 Mon Sep 17 00:00:00 2001 From: garmr Date: Tue, 27 Jan 2026 16:56:50 -0800 Subject: [PATCH 05/22] feat(unbounded): add custom HTTP client with direct outbound for RTC options --- protocol/unbounded/outbound.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 50cde25..eb40ea7 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -7,9 +7,11 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" + "errors" "log" "math/big" "net" + "net/http" "os" "time" @@ -18,10 +20,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/constant" singlog "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" C "github.com/getlantern/lantern-box/constant" "github.com/getlantern/lantern-box/option" @@ -153,6 +157,23 @@ func NewOutbound( } rtcOpt.Net = rtcNet + outboundManager := service.FromContext[adapter.OutboundManager](ctx) + direct, exists := outboundManager.Outbound(constant.TypeDirect) + if !exists { + return nil, errors.New("Could not find direct outbound") + } + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + return direct.DialContext(ctx, network, M.ParseSocksaddr(addr)) + } + rtcOpt.HTTPClient = &http.Client{ + Transport: &http.Transport{ + Dial: func(network, addr string) (net.Conn, error) { + return dialContext(ctx, network, addr) + }, + DialContext: dialContext, + }, + } + BFConn, ui, err := UBClientcore.NewBroflake(bfOpt, rtcOpt, egOpt) if err != nil { return nil, err From 96206c6bbd8c5817e9a6c6118d5034393ec3a88f Mon Sep 17 00:00:00 2001 From: garmr Date: Wed, 28 Jan 2026 10:49:14 -0800 Subject: [PATCH 06/22] update unbounded --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f19a612..aee7585 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/Jigsaw-Code/outline-sdk v0.0.19 github.com/Jigsaw-Code/outline-sdk/x v0.0.2 github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 - github.com/getlantern/broflake v0.0.0-20260128002846-de79df6a2853 + github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b github.com/getlantern/lantern-water v0.0.0-20250331153903-07abebe611e8 github.com/gobwas/ws v1.4.0 github.com/pion/transport/v3 v3.0.7 diff --git a/go.sum b/go.sum index 998e95f..63caba2 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/gaukas/wazerofs v0.1.0 h1:wIkW1bAxSnpaaVkQ5LOb1tm1BXdVap3eKjJpVWIqt2E github.com/gaukas/wazerofs v0.1.0/go.mod h1:+JECB9Fwt0taPqSgHckG9lmT3tcoVK+9VJozTsq9UlI= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 h1:w2/RqYPw7PbTYfUMS2aToD5DMKLBnQed+fkTEYTKAqQ= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52/go.mod h1:PrNR8tMXO26YNs8K9653XCUH7u2Kv4OdfFC3Ke1GsX0= -github.com/getlantern/broflake v0.0.0-20260128002846-de79df6a2853 h1:cF6ky5h335dZp+2Ae94mwnOdAfLTq1X5kkNPDIZi93k= -github.com/getlantern/broflake v0.0.0-20260128002846-de79df6a2853/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= +github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b h1:doEOWdRX7o8cKqNR42MYOxS5sQQoPsUihK+5CujrZvs= +github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= From 4725950db23050a0ee8e78d4fb7033bbc2f6c95f Mon Sep 17 00:00:00 2001 From: garmr Date: Fri, 6 Feb 2026 17:42:24 -0800 Subject: [PATCH 07/22] log dials, start quiclayer listener in start method --- protocol/unbounded/net.go | 51 ++++++++++++++++++++++++++-------- protocol/unbounded/outbound.go | 10 +++++-- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/protocol/unbounded/net.go b/protocol/unbounded/net.go index 20d994b..4c48900 100644 --- a/protocol/unbounded/net.go +++ b/protocol/unbounded/net.go @@ -7,6 +7,7 @@ import ( "github.com/pion/transport/v3" "github.com/pion/transport/v3/stdnet" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -20,9 +21,25 @@ type rtcNet struct { *stdnet.Net ctx context.Context dialer N.Dialer + logger log.ContextLogger } -func newRTCNet(ctx context.Context, dialer N.Dialer) (*rtcNet, error) { +type logDialer struct { + d N.Dialer + logger log.ContextLogger +} + +func (d *logDialer) DialContext(ctx context.Context, network string, destination metadata.Socksaddr) (net.Conn, error) { + d.logger.Debug("dialing with sing-box dialer", "network", network, "destination", destination) + return d.d.DialContext(ctx, network, destination) +} + +func (d *logDialer) ListenPacket(ctx context.Context, destination metadata.Socksaddr) (net.PacketConn, error) { + d.logger.Debug("listening with sing-box dialer", "destination", destination) + return d.d.ListenPacket(ctx, destination) +} + +func newRTCNet(ctx context.Context, dialer N.Dialer, logger log.ContextLogger) (*rtcNet, error) { snet, err := stdnet.NewNet() if err != nil { return nil, err @@ -30,7 +47,8 @@ func newRTCNet(ctx context.Context, dialer N.Dialer) (*rtcNet, error) { return &rtcNet{ Net: snet, ctx: ctx, - dialer: dialer, + dialer: &logDialer{dialer, logger}, + logger: logger, }, nil } @@ -46,7 +64,7 @@ func (n *rtcNet) ListenPacket(network string, address string) (net.PacketConn, e func (n *rtcNet) DialUDP(network string, laddr *net.UDPAddr, raddr *net.UDPAddr) (transport.UDPConn, error) { destination := metadata.SocksaddrFromNet(raddr) - conn, err := n.dialer.DialContext(n.ctx, "udp", destination) + conn, err := n.dialer.DialContext(n.ctx, network, destination) if err != nil { return nil, err } @@ -65,7 +83,7 @@ func (n *rtcNet) DialUDP(network string, laddr *net.UDPAddr, raddr *net.UDPAddr) func (n *rtcNet) DialTCP(network string, laddr *net.TCPAddr, raddr *net.TCPAddr) (transport.TCPConn, error) { destination := metadata.SocksaddrFromNet(raddr) - conn, err := n.dialer.DialContext(n.ctx, "tcp", destination) + conn, err := n.dialer.DialContext(n.ctx, network, destination) if err != nil { return nil, err } @@ -83,19 +101,30 @@ func (n *rtcNet) DialTCP(network string, laddr *net.TCPAddr, raddr *net.TCPAddr) } // maybe well implement these later. might be useful -// func (n *rtcNet) ResolveIPAddr(network string, address string) (*net.IPAddr, error) {} -// func (n *rtcNet) ResolveUDPAddr(network string, address string) (*net.UDPAddr, error) {} -// func (n *rtcNet) ResolveTCPAddr(network string, address string) (*net.TCPAddr, error) {} +func (n *rtcNet) ResolveIPAddr(network string, address string) (*net.IPAddr, error) { + n.logger.Debug("resolving IP address", "network", network, "address", address) + return n.Net.ResolveIPAddr(network, address) +} + +func (n *rtcNet) ResolveUDPAddr(network string, address string) (*net.UDPAddr, error) { + n.logger.Debug("resolving UDP address", "network", network, "address", address) + return n.Net.ResolveUDPAddr(network, address) +} + +func (n *rtcNet) ResolveTCPAddr(network string, address string) (*net.TCPAddr, error) { + n.logger.Debug("resolving TCP address", "network", network, "address", address) + return n.Net.ResolveTCPAddr(network, address) +} func (n *rtcNet) CreateDialer(dialer *net.Dialer) transport.Dialer { - return &rtcDialer{n.ctx, n.dialer} + return &rtcDialer{dialer, n} } type rtcDialer struct { - ctx context.Context - dialer N.Dialer + dialer *net.Dialer + net *rtcNet } func (d *rtcDialer) Dial(network, address string) (net.Conn, error) { - return d.dialer.DialContext(d.ctx, network, metadata.ParseSocksaddr(address)) + return d.dialer.Dial(network, address) } diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index eb40ea7..087c323 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -151,7 +151,7 @@ func NewOutbound( if err != nil { return nil, err } - rtcNet, err := newRTCNet(ctx, outboundDialer) + rtcNet, err := newRTCNet(ctx, outboundDialer, logger) if err != nil { return nil, err } @@ -201,7 +201,6 @@ func NewOutbound( ql: QUICLayer, } - go QUICLayer.ListenAndMaintainQUICConnection() return o, nil } @@ -221,6 +220,13 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return nil, os.ErrInvalid } +func (h *Outbound) Start(stage adapter.StartStage) error { + if stage == adapter.StartStatePostStart { + go h.ql.ListenAndMaintainQUICConnection() + } + return nil +} + func (h *Outbound) Close() error { h.ql.Close() h.ui.Stop() From bd6441cee5c0e34d9a472197572c679ec4409ee3 Mon Sep 17 00:00:00 2001 From: garmr Date: Tue, 10 Feb 2026 10:34:53 -0800 Subject: [PATCH 08/22] replace direct outbound with sing-box dialer --- protocol/unbounded/outbound.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 087c323..3c8390b 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -7,7 +7,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" - "errors" "log" "math/big" "net" @@ -20,12 +19,10 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/constant" singlog "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" C "github.com/getlantern/lantern-box/constant" "github.com/getlantern/lantern-box/option" @@ -156,14 +153,8 @@ func NewOutbound( return nil, err } rtcOpt.Net = rtcNet - - outboundManager := service.FromContext[adapter.OutboundManager](ctx) - direct, exists := outboundManager.Outbound(constant.TypeDirect) - if !exists { - return nil, errors.New("Could not find direct outbound") - } dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { - return direct.DialContext(ctx, network, M.ParseSocksaddr(addr)) + return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) } rtcOpt.HTTPClient = &http.Client{ Transport: &http.Transport{ From b58a682c5311ad380cc521122ef42f913e6dd94b Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Tue, 17 Feb 2026 17:08:38 -0800 Subject: [PATCH 09/22] bump unbounded to latest mainline --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index aee7585..e7c720d 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/Jigsaw-Code/outline-sdk v0.0.19 github.com/Jigsaw-Code/outline-sdk/x v0.0.2 github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 - github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b + github.com/getlantern/broflake v0.0.0-20260215213912-5f68e35de272 github.com/getlantern/lantern-water v0.0.0-20250331153903-07abebe611e8 github.com/gobwas/ws v1.4.0 github.com/pion/transport/v3 v3.0.7 diff --git a/go.sum b/go.sum index 63caba2..921e1f1 100644 --- a/go.sum +++ b/go.sum @@ -169,6 +169,8 @@ github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 h1:w2/RqYPw7Pb github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52/go.mod h1:PrNR8tMXO26YNs8K9653XCUH7u2Kv4OdfFC3Ke1GsX0= github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b h1:doEOWdRX7o8cKqNR42MYOxS5sQQoPsUihK+5CujrZvs= github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= +github.com/getlantern/broflake v0.0.0-20260215213912-5f68e35de272 h1:oKungK1zgRUTACFmIulNf7U/KahaIKNK0vubLINhnss= +github.com/getlantern/broflake v0.0.0-20260215213912-5f68e35de272/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= From aff24f98eb48e1238ef37422d5d15da3cdc0f64b Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Tue, 17 Feb 2026 18:15:13 -0800 Subject: [PATCH 10/22] move hidden goroutine creation to the start function --- protocol/unbounded/outbound.go | 58 ++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 3c8390b..eb0c39a 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" + "fmt" "log" "math/big" "net" @@ -50,6 +51,9 @@ type Outbound struct { dial UBClientcore.SOCKS5Dialer ui UBClientcore.UI ql *UBClientcore.QUICLayer + rtcOpt *UBClientcore.WebRTCOptions + bfOpt *UBClientcore.BroflakeOptions + egOpt *UBClientcore.EgressOptions } func NewOutbound( @@ -165,19 +169,6 @@ func NewOutbound( }, } - BFConn, ui, err := UBClientcore.NewBroflake(bfOpt, rtcOpt, egOpt) - if err != nil { - return nil, err - } - - // TODO: plumb through a real TLS cert and get rid of the self-signed generator? - QUICLayer, err := UBClientcore.NewQUICLayer(BFConn, generateSelfSignedTLSConfig()) - if err != nil { - return nil, err - } - - dialer := UBClientcore.CreateSOCKS5Dialer(QUICLayer) - o := &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions( C.TypeUnbounded, @@ -185,11 +176,10 @@ func NewOutbound( []string{N.NetworkTCP}, // XXX: Unbounded only supports TCP (not UDP) for now options.DialerOptions, ), - logger: logger, - broflakeConn: BFConn, - dial: dialer, - ui: ui, - ql: QUICLayer, + logger: logger, + rtcOpt: rtcOpt, + bfOpt: bfOpt, + egOpt: egOpt, } return o, nil @@ -203,6 +193,10 @@ func (h *Outbound) DialContext( // XXX: this is the log pattern for N.NetworkTCP h.logger.InfoContext(ctx, "outbound connection to ", destination) + if h.dial == nil { + return nil, fmt.Errorf("unbounded not ready") + } + // XXX: network is ignored by Unbounded's SOCKS5 dialer return h.dial(ctx, network, destination.String()) } @@ -213,14 +207,38 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n func (h *Outbound) Start(stage adapter.StartStage) error { if stage == adapter.StartStatePostStart { + BFConn, ui, err := UBClientcore.NewBroflake(h.bfOpt, h.rtcOpt, h.egOpt) + if err != nil { + return err + } + + // TODO: plumb through a real TLS cert and get rid of the self-signed generator? + QUICLayer, err := UBClientcore.NewQUICLayer(BFConn, generateSelfSignedTLSConfig()) + if err != nil { + return err + } + + dialer := UBClientcore.CreateSOCKS5Dialer(QUICLayer) + + h.broflakeConn = BFConn + h.dial = dialer + h.ui = ui + h.ql = QUICLayer + go h.ql.ListenAndMaintainQUICConnection() } return nil } func (h *Outbound) Close() error { - h.ql.Close() - h.ui.Stop() + if h.ql != nil { + h.ql.Close() + } + + if h.ui != nil { + h.ui.Stop() + } + return nil } From b621dd26d6854aeb971fe3a51911f7ae9c2ec179 Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Sat, 21 Feb 2026 12:15:22 -0800 Subject: [PATCH 11/22] unbounded: refactor and plumb through options for reverse TLS --- option/unbounded.go | 1 + protocol/unbounded/outbound.go | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/option/unbounded.go b/option/unbounded.go index 2338943..0610705 100644 --- a/option/unbounded.go +++ b/option/unbounded.go @@ -12,6 +12,7 @@ import ( type UnboundedOutboundOptions struct { option.DialerOptions option.ServerOptions + InsecureDoNotVerifyClientCert bool `json:"insecure_do_not_verify_client_cert,omitempty"` // BroflakeOptions CTableSize int `json:"c_table_size,omitempty"` PTableSize int `json:"p_table_size,omitempty"` diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index eb0c39a..15e8cc0 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -29,8 +29,6 @@ import ( "github.com/getlantern/lantern-box/option" ) -// WIP usage: edit sing-box/include/registry.go to import and register this protocol - type logAdapter struct { singBoxLogger singlog.ContextLogger } @@ -54,6 +52,7 @@ type Outbound struct { rtcOpt *UBClientcore.WebRTCOptions bfOpt *UBClientcore.BroflakeOptions egOpt *UBClientcore.EgressOptions + tlsConfig *tls.Config } func NewOutbound( @@ -176,10 +175,11 @@ func NewOutbound( []string{N.NetworkTCP}, // XXX: Unbounded only supports TCP (not UDP) for now options.DialerOptions, ), - logger: logger, - rtcOpt: rtcOpt, - bfOpt: bfOpt, - egOpt: egOpt, + logger: logger, + rtcOpt: rtcOpt, + bfOpt: bfOpt, + egOpt: egOpt, + tlsConfig: generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert), } return o, nil @@ -212,8 +212,7 @@ func (h *Outbound) Start(stage adapter.StartStage) error { return err } - // TODO: plumb through a real TLS cert and get rid of the self-signed generator? - QUICLayer, err := UBClientcore.NewQUICLayer(BFConn, generateSelfSignedTLSConfig()) + QUICLayer, err := UBClientcore.NewQUICLayer(BFConn, h.tlsConfig) if err != nil { return err } @@ -242,8 +241,8 @@ func (h *Outbound) Close() error { return nil } -// TODO: delete me -func generateSelfSignedTLSConfig() *tls.Config { +// Reverse TLS, since the Unbounded client is the QUIC server +func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool) *tls.Config { key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { panic(err) @@ -261,8 +260,16 @@ func generateSelfSignedTLSConfig() *tls.Config { if err != nil { panic(err) } + + clientAuth := tls.RequireAndVerifyClientCert + + if insecureDoNotVerifyClientCert { + clientAuth = tls.NoClientCert + } + return &tls.Config{ Certificates: []tls.Certificate{tlsCert}, NextProtos: []string{"broflake"}, + ClientAuth: clientAuth, } } From 2039f3bd922781e50b38fff7694c0bcdfa32ac4b Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Sat, 21 Feb 2026 15:08:29 -0800 Subject: [PATCH 12/22] unbounded: plumb through CA cert for reverse TLS --- option/unbounded.go | 3 ++- protocol/unbounded/outbound.go | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/option/unbounded.go b/option/unbounded.go index 0610705..82168ab 100644 --- a/option/unbounded.go +++ b/option/unbounded.go @@ -12,7 +12,8 @@ import ( type UnboundedOutboundOptions struct { option.DialerOptions option.ServerOptions - InsecureDoNotVerifyClientCert bool `json:"insecure_do_not_verify_client_cert,omitempty"` + InsecureDoNotVerifyClientCert bool `json:"insecure_do_not_verify_client_cert,omitempty"` + EgressCA string `json:"egress_ca,omitempty"` // BroflakeOptions CTableSize int `json:"c_table_size,omitempty"` PTableSize int `json:"p_table_size,omitempty"` diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 15e8cc0..7b71626 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -179,7 +179,7 @@ func NewOutbound( rtcOpt: rtcOpt, bfOpt: bfOpt, egOpt: egOpt, - tlsConfig: generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert), + tlsConfig: generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert, options.EgressCA), } return o, nil @@ -242,7 +242,7 @@ func (h *Outbound) Close() error { } // Reverse TLS, since the Unbounded client is the QUIC server -func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool) *tls.Config { +func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool, egressCA string) *tls.Config { key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { panic(err) @@ -261,6 +261,15 @@ func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool) *tls.Config panic(err) } + caCertPool := x509.NewCertPool() + + if egressCA != "" { + ok := caCertPool.AppendCertsFromPEM([]byte(egressCA)) + if !ok { + panic("an egress CA cert was configured, but it could not be appended") + } + } + clientAuth := tls.RequireAndVerifyClientCert if insecureDoNotVerifyClientCert { @@ -271,5 +280,6 @@ func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool) *tls.Config Certificates: []tls.Certificate{tlsCert}, NextProtos: []string{"broflake"}, ClientAuth: clientAuth, + ClientCAs: caCertPool, } } From be1722f3a278333daed8c23352b60d17928a6a54 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 27 Feb 2026 05:24:37 -0700 Subject: [PATCH 13/22] Fixing go mod --- go.mod | 40 ++++++------------------ go.sum | 97 ++++++++++++++++++++++++---------------------------------- 2 files changed, 50 insertions(+), 87 deletions(-) diff --git a/go.mod b/go.mod index 20978fb..b5de02a 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect github.com/nwaples/rardecode v1.1.2 // indirect @@ -68,9 +68,9 @@ require ( github.com/ulikunitz/xz v0.5.10 // indirect github.com/wlynxg/anet v0.0.5 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect ) require ( @@ -106,7 +106,6 @@ require ( github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/caddyserver/certmagic v0.23.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect - github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coder/websocket v1.8.13 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect @@ -114,20 +113,15 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect - github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gaissmai/bart v0.11.1 // indirect github.com/gaukas/wazerofs v0.1.0 // indirect - github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect - github.com/getlantern/errors v1.0.1 // indirect - github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect - github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect + github.com/getlantern/errors v1.0.4 // indirect github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect - github.com/getlantern/keepcurrent v0.0.0-20221014183517-fcee77376b89 // indirect - github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect + github.com/getlantern/ops v0.0.0-20230424193308-26325dfed3cf // indirect github.com/go-chi/chi/v5 v5.2.2 // indirect github.com/go-chi/render v1.0.3 // indirect github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect @@ -136,14 +130,12 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-stack/stack v1.8.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/goccy/go-yaml v1.17.1 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect github.com/gofrs/uuid/v5 v5.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect @@ -151,7 +143,6 @@ require ( github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect @@ -161,7 +152,6 @@ require ( github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/klauspost/pgzip v1.2.5 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect @@ -175,32 +165,27 @@ require ( github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 // indirect github.com/metacubex/utls v1.8.4 // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect - github.com/mholt/archiver/v3 v3.5.1 // indirect github.com/miekg/dns v1.1.67 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mschoch/smat v0.2.0 // indirect - github.com/nwaples/rardecode v1.1.2 // indirect - github.com/oschwald/geoip2-golang v1.9.0 // indirect - github.com/oschwald/maxminddb-golang v1.13.1 // indirect - github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect - github.com/pion/ice/v2 v2.2.6 // indirect + github.com/pion/ice/v2 v2.3.24 // indirect github.com/pion/interceptor v0.1.40 // indirect github.com/pion/logging v0.2.4 // indirect - github.com/pion/mdns v0.0.5 // indirect + github.com/pion/mdns v0.0.12 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.15 // indirect github.com/pion/rtp v1.8.19 // indirect github.com/pion/sctp v1.8.39 // indirect github.com/pion/sdp/v3 v3.0.14 // indirect - github.com/pion/srtp/v2 v2.0.9 // indirect + github.com/pion/srtp/v2 v2.0.18 // indirect github.com/pion/stun v0.6.1 // indirect github.com/pion/transport v0.14.1 // indirect github.com/pion/transport/v2 v2.2.10 // indirect - github.com/pion/turn/v2 v2.0.8 // indirect - github.com/pion/webrtc/v3 v3.1.42 // indirect + github.com/pion/turn/v2 v2.1.3 // indirect + github.com/pion/webrtc/v3 v3.2.40 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect @@ -241,20 +226,16 @@ require ( github.com/tevino/abool/v2 v2.1.0 // indirect github.com/tidwall/btree v1.6.0 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect - github.com/ulikunitz/xz v0.5.10 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/mod v0.31.0 // indirect @@ -267,7 +248,6 @@ require ( golang.org/x/tools v0.40.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/go.sum b/go.sum index d7ddb7e..e30d004 100644 --- a/go.sum +++ b/go.sum @@ -165,27 +165,31 @@ github.com/gaukas/wazerofs v0.1.0 h1:wIkW1bAxSnpaaVkQ5LOb1tm1BXdVap3eKjJpVWIqt2E github.com/gaukas/wazerofs v0.1.0/go.mod h1:+JECB9Fwt0taPqSgHckG9lmT3tcoVK+9VJozTsq9UlI= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52 h1:w2/RqYPw7PbTYfUMS2aToD5DMKLBnQed+fkTEYTKAqQ= github.com/getlantern/algeneva v0.0.0-20250307163401-1824e7b54f52/go.mod h1:PrNR8tMXO26YNs8K9653XCUH7u2Kv4OdfFC3Ke1GsX0= -github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b h1:doEOWdRX7o8cKqNR42MYOxS5sQQoPsUihK+5CujrZvs= -github.com/getlantern/broflake v0.0.0-20260128184723-c0316859616b/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= github.com/getlantern/broflake v0.0.0-20260215213912-5f68e35de272 h1:oKungK1zgRUTACFmIulNf7U/KahaIKNK0vubLINhnss= github.com/getlantern/broflake v0.0.0-20260215213912-5f68e35de272/go.mod h1:hacSrhmDkUK8laEnfOp51e7PAfhFI/Lbie0MH8/uup4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= -github.com/getlantern/errors v1.0.1 h1:XukU2whlh7OdpxnkXhNH9VTLVz0EVPGKDV5K0oWhvzw= +github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA= +github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo= github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= +github.com/getlantern/errors v1.0.4 h1:i2iR1M9GKj4WuingpNqJ+XQEw6i6dnAgKAmLj6ZB3X0= +github.com/getlantern/errors v1.0.4/go.mod h1:/Foq8jtSDGP8GOXzAjeslsC4Ar/3kB+UiQH+WyV4pzY= github.com/getlantern/geo v0.0.0-20241129152027-2fc88c10f91e h1:vpikNz6IzvEoqVYmiK5Uq+lE4TCzvMDqbZdxFbtGK1g= github.com/getlantern/geo v0.0.0-20241129152027-2fc88c10f91e/go.mod h1:RjQ0krF8NTCc5xo2Q1995/vZBnYg33h8svn15do7dLg= github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 h1:NlQedYmPI3pRAXJb+hLVVDGqfvvXGRPV8vp7XOjKAZ0= github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU= -github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0= github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o= +github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc h1:sue+aeVx7JF5v36H1HfvcGFImLpSD5goj8d+MitovDU= +github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc/go.mod h1:D9RWpXy/EFPYxiKUURo2TB8UBosbqkiLhttRrZYtvqM= github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc= github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA= github.com/getlantern/keepcurrent v0.0.0-20221014183517-fcee77376b89 h1:gjlTAADW8ZUrIey+u1ZtbVlI91bqI0Bu+GBxvRlBBqo= github.com/getlantern/keepcurrent v0.0.0-20221014183517-fcee77376b89/go.mod h1:EtJEobtQH/HiQsZLyRjlrnq/fu7vfgnTMzhbmUqkZ3M= github.com/getlantern/lantern-water v0.0.0-20260130212632-d5ea08838250 h1:xculJyC6hS0kNSQKWBP1FQbpSVmeJyhUGID804jgKCA= github.com/getlantern/lantern-water v0.0.0-20260130212632-d5ea08838250/go.mod h1:ZpSOrcdJkmb8MvaQn6mxaidxshlyi+RJLUerhW4L5Lo= -github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA= github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= +github.com/getlantern/ops v0.0.0-20220713155959-1315d978fff7/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= +github.com/getlantern/ops v0.0.0-20230424193308-26325dfed3cf h1:q8nsH0Lx9fP8HY6T9rA1zogvOzO9JtbUI5BXkh7wxxI= +github.com/getlantern/ops v0.0.0-20230424193308-26325dfed3cf/go.mod h1:R7HfJVLsnSeqaDWkiUlU+ANBjac4oYmXGrrps8vW7CM= github.com/getlantern/samizdat v0.0.2 h1:PkMu6jsfUz7DLZUH2xh548XfzgPASmq5CajZyUKj/9Y= github.com/getlantern/samizdat v0.0.2/go.mod h1:uEeykQSW2/6rTjfPlj3MTTo59poSHXfAHTGgzYDkbr0= github.com/getlantern/sing v0.7.18-lantern h1:QKGgIUA3LwmKYP/7JlQTRkxj9jnP4cX2Q/B+nd8XEjo= @@ -217,17 +221,16 @@ github.com/go-llsqlite/crawshaw v0.4.0/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYz github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -274,6 +277,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -285,7 +289,7 @@ github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdF github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -399,15 +403,9 @@ github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= @@ -422,81 +420,71 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus= -github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E= github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU= -github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig= -github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE= +github.com/pion/ice/v2 v2.3.24 h1:RYgzhH/u5lH0XO+ABatVKCtRd+4U1GEaCXSMjNr13tI= +github.com/pion/ice/v2 v2.3.24/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= -github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= -github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= -github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= +github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= +github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= +github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= -github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= -github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= -github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= -github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ= -github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4= +github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo= +github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= github.com/pion/stun v0.3.1/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= github.com/pion/stun v0.3.2/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= -github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport v0.8.6/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno= github.com/pion/transport v0.8.8/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8= -github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= -github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= -github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g= -github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg= github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc= +github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= +github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/turn v1.3.5/go.mod h1:zGPB7YYB/HTE9MWn0Sbznz8NtyfeVeanZ834cG/MXu0= github.com/pion/turn v1.3.7 h1:/nyM2XrlZILD7KKfnh0oYEBTRG5JlbH21ibjluRoCeo= github.com/pion/turn v1.3.7/go.mod h1:js0LBFqMcKAlaWAXoYqNjefGI7kfJCrkCBfHGuTToXE= -github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= -github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= +github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA= +github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= -github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= -github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA= -github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA= +github.com/pion/webrtc/v3 v3.2.40 h1:Wtfi6AZMQg+624cvCXUuSmrKWepSB7zfgYDOYqsSOVU= +github.com/pion/webrtc/v3 v3.2.40/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY= github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -608,13 +596,12 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= @@ -679,6 +666,7 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= @@ -689,6 +677,7 @@ go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ7 go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= @@ -725,10 +714,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= @@ -766,10 +754,9 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -817,11 +804,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -831,10 +816,9 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -845,10 +829,9 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 30145c2aa34ed0293387282c3945263a47ec2652 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 27 Feb 2026 05:31:40 -0700 Subject: [PATCH 14/22] Fix duplicate imports and whitespace in register.go and proxy.go Deduplicate algeneva and water imports that were added twice in register.go (merge conflict artifact). Fix spaces-to-tab indentation for TypeSamizdat in proxy.go. Co-Authored-By: Claude Opus 4.6 --- constant/proxy.go | 2 +- protocol/register.go | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/constant/proxy.go b/constant/proxy.go index 4f69b5e..4c016c9 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -4,7 +4,7 @@ const ( TypeAmnezia = "amnezia" TypeALGeneva = "algeneva" TypeOutline = "outline" - TypeSamizdat = "samizdat" + TypeSamizdat = "samizdat" TypeWATER = "water" TypeUnbounded = "unbounded" ) diff --git a/protocol/register.go b/protocol/register.go index db77010..57f2803 100644 --- a/protocol/register.go +++ b/protocol/register.go @@ -12,12 +12,9 @@ import ( "github.com/getlantern/lantern-box/protocol/algeneva" "github.com/getlantern/lantern-box/protocol/amnezia" "github.com/getlantern/lantern-box/protocol/group" - "github.com/getlantern/lantern-box/protocol/unbounded" - "github.com/getlantern/lantern-box/protocol/water" - - "github.com/getlantern/lantern-box/protocol/algeneva" "github.com/getlantern/lantern-box/protocol/outline" "github.com/getlantern/lantern-box/protocol/samizdat" + "github.com/getlantern/lantern-box/protocol/unbounded" "github.com/getlantern/lantern-box/protocol/water" ) From 6c9ab437fe33986d97d9c9c31751ec21312a0cee Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 27 Feb 2026 05:39:26 -0700 Subject: [PATCH 15/22] Add HTTPClient and STUNServers to unbounded options Allow callers to inject an http.Client and a static list of STUN servers into UnboundedOutboundOptions. When HTTPClient is set, it is used directly instead of building one from the sing-box dialer. When STUNServers is set (and STUNBatch is not), a batch function is created that returns from the provided list. Co-Authored-By: Claude Opus 4.6 --- option/unbounded.go | 14 +++++++++----- protocol/unbounded/outbound.go | 31 ++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/option/unbounded.go b/option/unbounded.go index 82168ab..01aa90f 100644 --- a/option/unbounded.go +++ b/option/unbounded.go @@ -1,6 +1,8 @@ package option import ( + "net/http" + "github.com/sagernet/sing-box/option" ) @@ -20,12 +22,14 @@ type UnboundedOutboundOptions struct { BusBufferSz int `json:"bus_buffer_sz,omitempty"` Netstated string `json:"netstated,omitempty"` // WebRTCOptions - DiscoverySrv string `json:"discovery_srv,omitempty"` - DiscoveryEndpoint string `json:"discovery_endpoint,omitempty"` - GenesisAddr string `json:"genesis_addr,omitempty"` - NATFailTimeout int `json:"nat_fail_timeout,omitempty"` - STUNBatchSize int `json:"stun_batch_size,omitempty"` + DiscoverySrv string `json:"discovery_srv,omitempty"` + DiscoveryEndpoint string `json:"discovery_endpoint,omitempty"` + GenesisAddr string `json:"genesis_addr,omitempty"` + NATFailTimeout int `json:"nat_fail_timeout,omitempty"` + STUNBatchSize int `json:"stun_batch_size,omitempty"` + STUNServers []string `json:"stun_servers,omitempty"` STUNBatch func(size uint32) (batch []string, err error) + HTTPClient *http.Client Tag string `json:"tag,omitempty"` Patience int `json:"patience,omitempty"` ErrorBackoff int `json:"error_backoff,omitempty"` diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 7b71626..428906a 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -102,6 +102,15 @@ func NewOutbound( if options.STUNBatch != nil { rtcOpt.STUNBatch = options.STUNBatch + } else if len(options.STUNServers) > 0 { + servers := make([]string, len(options.STUNServers)) + copy(servers, options.STUNServers) + rtcOpt.STUNBatch = func(size uint32) ([]string, error) { + if int(size) >= len(servers) { + return servers, nil + } + return servers[:size], nil + } } if options.Tag != "" { @@ -156,16 +165,20 @@ func NewOutbound( return nil, err } rtcOpt.Net = rtcNet - dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { - return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - } - rtcOpt.HTTPClient = &http.Client{ - Transport: &http.Transport{ - Dial: func(network, addr string) (net.Conn, error) { - return dialContext(ctx, network, addr) + if options.HTTPClient != nil { + rtcOpt.HTTPClient = options.HTTPClient + } else { + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + } + rtcOpt.HTTPClient = &http.Client{ + Transport: &http.Transport{ + Dial: func(network, addr string) (net.Conn, error) { + return dialContext(ctx, network, addr) + }, + DialContext: dialContext, }, - DialContext: dialContext, - }, + } } o := &Outbound{ From f5ccc465f876dd1ed28037eae380c2e429552bba Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 27 Feb 2026 07:43:01 -0700 Subject: [PATCH 16/22] Add unbounded and water to supported protocols --- protocol/register.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/register.go b/protocol/register.go index 57f2803..19b2a4f 100644 --- a/protocol/register.go +++ b/protocol/register.go @@ -24,6 +24,8 @@ var supportedProtocols = []string{ "amnezia", "outline", "samizdat", + "unbounded", + "water", // sing-box built-in protocols "http", From 91d83880d5e4e9440a8b8c05a43eefdf48216031 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 27 Feb 2026 09:29:35 -0700 Subject: [PATCH 17/22] Add UDP-over-TCP (UoT) support to unbounded outbound Use sing's uot.Client to tunnel UDP traffic over Unbounded's TCP-only SOCKS5 connection, following the same pattern as the samizdat outbound. The outbound now advertises both TCP and UDP network support. Co-Authored-By: Claude Opus 4.6 --- protocol/unbounded/outbound.go | 46 ++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 428906a..5f39846 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -24,6 +24,7 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" C "github.com/getlantern/lantern-box/constant" "github.com/getlantern/lantern-box/option" @@ -47,6 +48,7 @@ type Outbound struct { logger logger.ContextLogger broflakeConn *UBClientcore.BroflakeConn dial UBClientcore.SOCKS5Dialer + uotClient *uot.Client ui UBClientcore.UI ql *UBClientcore.QUICLayer rtcOpt *UBClientcore.WebRTCOptions @@ -55,6 +57,22 @@ type Outbound struct { tlsConfig *tls.Config } +// unboundedDialer adapts the Unbounded SOCKS5 dialer to the N.Dialer interface for uot.Client. +type unboundedDialer struct { + outbound *Outbound +} + +func (d *unboundedDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if d.outbound.dial == nil { + return nil, fmt.Errorf("unbounded not ready") + } + return d.outbound.dial(ctx, network, destination.String()) +} + +func (d *unboundedDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + func NewOutbound( ctx context.Context, router adapter.Router, @@ -185,7 +203,7 @@ func NewOutbound( Adapter: outbound.NewAdapterWithDialerOptions( C.TypeUnbounded, tag, - []string{N.NetworkTCP}, // XXX: Unbounded only supports TCP (not UDP) for now + []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions, ), logger: logger, @@ -195,6 +213,11 @@ func NewOutbound( tlsConfig: generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert, options.EgressCA), } + o.uotClient = &uot.Client{ + Dialer: &unboundedDialer{outbound: o}, + Version: uot.Version, + } + return o, nil } @@ -203,19 +226,28 @@ func (h *Outbound) DialContext( network string, destination M.Socksaddr, ) (net.Conn, error) { - // XXX: this is the log pattern for N.NetworkTCP - h.logger.InfoContext(ctx, "outbound connection to ", destination) - if h.dial == nil { return nil, fmt.Errorf("unbounded not ready") } - // XXX: network is ignored by Unbounded's SOCKS5 dialer - return h.dial(ctx, network, destination.String()) + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + return h.dial(ctx, network, destination.String()) + case N.NetworkUDP: + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.DialContext(ctx, network, destination) + } + return nil, fmt.Errorf("unsupported network: %s", network) } +// ListenPacket creates a UoT packet connection through the Unbounded proxy. func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = h.Tag() + metadata.Destination = destination + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.ListenPacket(ctx, destination) } func (h *Outbound) Start(stage adapter.StartStage) error { From abbe92cb74deccafee32a3dd1de3af63a5e21cc1 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 27 Feb 2026 11:35:57 -0700 Subject: [PATCH 18/22] Use ECDSA P-256 and return errors instead of panicking in TLS config Replace 1024-bit RSA (insecure) with ECDSA P-256 for self-signed TLS cert generation. Return errors to caller instead of panicking so the outbound can fail gracefully during init. Co-Authored-By: Claude Opus 4.6 --- protocol/unbounded/outbound.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 5f39846..336ec8b 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -2,8 +2,9 @@ package unbounded import ( "context" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" - "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" @@ -199,6 +200,11 @@ func NewOutbound( } } + tlsConfig, err := generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert, options.EgressCA) + if err != nil { + return nil, fmt.Errorf("generate TLS config: %w", err) + } + o := &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions( C.TypeUnbounded, @@ -210,7 +216,7 @@ func NewOutbound( rtcOpt: rtcOpt, bfOpt: bfOpt, egOpt: egOpt, - tlsConfig: generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert, options.EgressCA), + tlsConfig: tlsConfig, } o.uotClient = &uot.Client{ @@ -287,23 +293,28 @@ func (h *Outbound) Close() error { } // Reverse TLS, since the Unbounded client is the QUIC server -func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool, egressCA string) *tls.Config { - key, err := rsa.GenerateKey(rand.Reader, 1024) +func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool, egressCA string) (*tls.Config, error) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - panic(err) + return nil, fmt.Errorf("generate ECDSA key: %w", err) } template := x509.Certificate{SerialNumber: big.NewInt(1)} certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) if err != nil { - panic(err) + return nil, fmt.Errorf("create certificate: %w", err) } - keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + + keyDER, err := x509.MarshalECPrivateKey(key) + if err != nil { + return nil, fmt.Errorf("marshal ECDSA key: %w", err) + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}) certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { - panic(err) + return nil, fmt.Errorf("load key pair: %w", err) } caCertPool := x509.NewCertPool() @@ -311,7 +322,7 @@ func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool, egressCA st if egressCA != "" { ok := caCertPool.AppendCertsFromPEM([]byte(egressCA)) if !ok { - panic("an egress CA cert was configured, but it could not be appended") + return nil, fmt.Errorf("failed to append egress CA cert") } } @@ -326,5 +337,5 @@ func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool, egressCA st NextProtos: []string{"broflake"}, ClientAuth: clientAuth, ClientCAs: caCertPool, - } + }, nil } From abb3a9baa66edc10049cae471c34bce718f90915 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 27 Feb 2026 11:41:58 -0700 Subject: [PATCH 19/22] Add json:"-" tags to non-serializable option fields Mark STUNBatch (func) and HTTPClient (*http.Client) with json:"-" so they are excluded from JSON marshalling. These are injection-only fields that cannot be represented in JSON config. Adds a round-trip marshalling test to verify the options struct serializes and deserializes correctly with these fields excluded. Co-Authored-By: Claude Opus 4.6 --- option/unbounded.go | 4 ++-- option/unbounded_test.go | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 option/unbounded_test.go diff --git a/option/unbounded.go b/option/unbounded.go index 01aa90f..a01de78 100644 --- a/option/unbounded.go +++ b/option/unbounded.go @@ -28,8 +28,8 @@ type UnboundedOutboundOptions struct { NATFailTimeout int `json:"nat_fail_timeout,omitempty"` STUNBatchSize int `json:"stun_batch_size,omitempty"` STUNServers []string `json:"stun_servers,omitempty"` - STUNBatch func(size uint32) (batch []string, err error) - HTTPClient *http.Client + STUNBatch func(size uint32) (batch []string, err error) `json:"-"` + HTTPClient *http.Client `json:"-"` Tag string `json:"tag,omitempty"` Patience int `json:"patience,omitempty"` ErrorBackoff int `json:"error_backoff,omitempty"` diff --git a/option/unbounded_test.go b/option/unbounded_test.go new file mode 100644 index 0000000..9e30b6b --- /dev/null +++ b/option/unbounded_test.go @@ -0,0 +1,41 @@ +package option + +import ( + "encoding/json" + "net/http" + "testing" +) + +func TestUnboundedOutboundOptionsMarshalJSON(t *testing.T) { + opts := UnboundedOutboundOptions{ + DiscoverySrv: "http://127.0.0.1:9000", + DiscoveryEndpoint: "/v1/signal", + STUNBatch: func(size uint32) ([]string, error) { + return []string{"stun:example.com:3478"}, nil + }, + HTTPClient: &http.Client{}, + } + + data, err := json.Marshal(opts) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + + var roundtrip UnboundedOutboundOptions + if err := json.Unmarshal(data, &roundtrip); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + + if roundtrip.DiscoverySrv != opts.DiscoverySrv { + t.Errorf("DiscoverySrv = %q, want %q", roundtrip.DiscoverySrv, opts.DiscoverySrv) + } + if roundtrip.DiscoveryEndpoint != opts.DiscoveryEndpoint { + t.Errorf("DiscoveryEndpoint = %q, want %q", roundtrip.DiscoveryEndpoint, opts.DiscoveryEndpoint) + } + if roundtrip.STUNBatch != nil { + t.Error("STUNBatch should be nil after round-trip (json:\"-\")") + } + if roundtrip.HTTPClient != nil { + t.Error("HTTPClient should be nil after round-trip (json:\"-\")") + } +} From ab0b1e199fe9642027d928288a09cb5a2e955dbd Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Sun, 1 Mar 2026 19:09:36 -0800 Subject: [PATCH 20/22] revert injection of STUNBatch function and http.Client, and implement working STUN batch function --- option/unbounded.go | 12 +++------ protocol/unbounded/outbound.go | 46 +++++++++++++++++----------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/option/unbounded.go b/option/unbounded.go index a01de78..aed7000 100644 --- a/option/unbounded.go +++ b/option/unbounded.go @@ -1,8 +1,6 @@ package option import ( - "net/http" - "github.com/sagernet/sing-box/option" ) @@ -28,12 +26,10 @@ type UnboundedOutboundOptions struct { NATFailTimeout int `json:"nat_fail_timeout,omitempty"` STUNBatchSize int `json:"stun_batch_size,omitempty"` STUNServers []string `json:"stun_servers,omitempty"` - STUNBatch func(size uint32) (batch []string, err error) `json:"-"` - HTTPClient *http.Client `json:"-"` - Tag string `json:"tag,omitempty"` - Patience int `json:"patience,omitempty"` - ErrorBackoff int `json:"error_backoff,omitempty"` - ConsumerSessionID string `json:"consumer_session_id,omitempty"` + Tag string `json:"tag,omitempty"` + Patience int `json:"patience,omitempty"` + ErrorBackoff int `json:"error_backoff,omitempty"` + ConsumerSessionID string `json:"consumer_session_id,omitempty"` // EgressOptions EgressAddr string `json:"egress_addr,omitempty"` EgressEndpoint string `json:"egress_endpoint,omitempty"` diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 336ec8b..923718a 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -11,6 +11,7 @@ import ( "fmt" "log" "math/big" + mathrand "math/rand" "net" "net/http" "os" @@ -119,17 +120,19 @@ func NewOutbound( rtcOpt.STUNBatchSize = uint32(options.STUNBatchSize) } - if options.STUNBatch != nil { - rtcOpt.STUNBatch = options.STUNBatch - } else if len(options.STUNServers) > 0 { - servers := make([]string, len(options.STUNServers)) - copy(servers, options.STUNServers) - rtcOpt.STUNBatch = func(size uint32) ([]string, error) { - if int(size) >= len(servers) { - return servers, nil - } - return servers[:size], nil + servers := make([]string, len(options.STUNServers)) + copy(servers, options.STUNServers) + + rtcOpt.STUNBatch = func(size uint32) (batch []string, err error) { + // At batch time, select N random servers from the list provided in the options + for i := 0; i < int(size) && len(servers) > 0; i++ { + idx := mathrand.Intn(len(servers)) + batch = append(batch, servers[idx]) + servers[idx] = servers[len(servers)-1] + servers = servers[:len(servers)-1] } + + return batch, nil } if options.Tag != "" { @@ -184,20 +187,17 @@ func NewOutbound( return nil, err } rtcOpt.Net = rtcNet - if options.HTTPClient != nil { - rtcOpt.HTTPClient = options.HTTPClient - } else { - dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { - return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - } - rtcOpt.HTTPClient = &http.Client{ - Transport: &http.Transport{ - Dial: func(network, addr string) (net.Conn, error) { - return dialContext(ctx, network, addr) - }, - DialContext: dialContext, + + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + } + rtcOpt.HTTPClient = &http.Client{ + Transport: &http.Transport{ + Dial: func(network, addr string) (net.Conn, error) { + return dialContext(ctx, network, addr) }, - } + DialContext: dialContext, + }, } tlsConfig, err := generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert, options.EgressCA) From acdc81d81f1b9ea3637b1a19a483120c6a078570 Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Sun, 1 Mar 2026 19:21:54 -0800 Subject: [PATCH 21/22] delete claude generated test --- option/unbounded_test.go | 41 ---------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 option/unbounded_test.go diff --git a/option/unbounded_test.go b/option/unbounded_test.go deleted file mode 100644 index 9e30b6b..0000000 --- a/option/unbounded_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package option - -import ( - "encoding/json" - "net/http" - "testing" -) - -func TestUnboundedOutboundOptionsMarshalJSON(t *testing.T) { - opts := UnboundedOutboundOptions{ - DiscoverySrv: "http://127.0.0.1:9000", - DiscoveryEndpoint: "/v1/signal", - STUNBatch: func(size uint32) ([]string, error) { - return []string{"stun:example.com:3478"}, nil - }, - HTTPClient: &http.Client{}, - } - - data, err := json.Marshal(opts) - if err != nil { - t.Fatalf("json.Marshal failed: %v", err) - } - - var roundtrip UnboundedOutboundOptions - if err := json.Unmarshal(data, &roundtrip); err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) - } - - if roundtrip.DiscoverySrv != opts.DiscoverySrv { - t.Errorf("DiscoverySrv = %q, want %q", roundtrip.DiscoverySrv, opts.DiscoverySrv) - } - if roundtrip.DiscoveryEndpoint != opts.DiscoveryEndpoint { - t.Errorf("DiscoveryEndpoint = %q, want %q", roundtrip.DiscoveryEndpoint, opts.DiscoveryEndpoint) - } - if roundtrip.STUNBatch != nil { - t.Error("STUNBatch should be nil after round-trip (json:\"-\")") - } - if roundtrip.HTTPClient != nil { - t.Error("HTTPClient should be nil after round-trip (json:\"-\")") - } -} From 824dd67eeccf8f3c5deec15d862a8c8e602064ab Mon Sep 17 00:00:00 2001 From: Noah Levenson Date: Sat, 7 Mar 2026 13:25:52 -0800 Subject: [PATCH 22/22] add SAN verification and plumb through egress server name for reverse TLS --- option/unbounded.go | 1 + protocol/unbounded/outbound.go | 37 ++++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/option/unbounded.go b/option/unbounded.go index aed7000..71c2679 100644 --- a/option/unbounded.go +++ b/option/unbounded.go @@ -14,6 +14,7 @@ type UnboundedOutboundOptions struct { option.ServerOptions InsecureDoNotVerifyClientCert bool `json:"insecure_do_not_verify_client_cert,omitempty"` EgressCA string `json:"egress_ca,omitempty"` + EgressServerName string `json:"egress_server_name,omitempty"` // BroflakeOptions CTableSize int `json:"c_table_size,omitempty"` PTableSize int `json:"p_table_size,omitempty"` diff --git a/protocol/unbounded/outbound.go b/protocol/unbounded/outbound.go index 923718a..8b6b485 100644 --- a/protocol/unbounded/outbound.go +++ b/protocol/unbounded/outbound.go @@ -200,7 +200,12 @@ func NewOutbound( }, } - tlsConfig, err := generateSelfSignedTLSConfig(options.InsecureDoNotVerifyClientCert, options.EgressCA) + tlsConfig, err := generateSelfSignedTLSConfig( + options.InsecureDoNotVerifyClientCert, + options.EgressCA, + options.EgressServerName, + ) + if err != nil { return nil, fmt.Errorf("generate TLS config: %w", err) } @@ -293,7 +298,11 @@ func (h *Outbound) Close() error { } // Reverse TLS, since the Unbounded client is the QUIC server -func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool, egressCA string) (*tls.Config, error) { +func generateSelfSignedTLSConfig( + insecureDoNotVerifyClientCert bool, + egressCA string, + egressServerName string, +) (*tls.Config, error) { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, fmt.Errorf("generate ECDSA key: %w", err) @@ -337,5 +346,29 @@ func generateSelfSignedTLSConfig(insecureDoNotVerifyClientCert bool, egressCA st NextProtos: []string{"broflake"}, ClientAuth: clientAuth, ClientCAs: caCertPool, + VerifyConnection: func(cs tls.ConnectionState) error { + if len(cs.PeerCertificates) == 0 { + return fmt.Errorf("no egress server client certificate") + } + + cert := cs.PeerCertificates[0] + + // We assume the egress server will present a DNS or IP SAN, so we don't check + // against URI or Email SANs... + + for _, dns := range cert.DNSNames { + if dns == egressServerName { + return nil + } + } + + for _, ip := range cert.IPAddresses { + if ip.String() == egressServerName { + return nil + } + } + + return fmt.Errorf("egress server client SAN not authorized") + }, }, nil }