From ff14220e08da9b2ed38231ec589e15b6f6c45dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 30 Jul 2023 20:26:09 +0800 Subject: [PATCH 01/20] platform: Update profile binary format --- experimental/libbox/command_server.go | 2 +- experimental/libbox/profile_import.go | 60 ++++++++++++++++++--------- test/go.mod | 22 +++++----- test/go.sum | 11 +++++ 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index dabb6f87..1aeb12af 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -83,7 +83,7 @@ func (s *CommandServer) listenUNIX() error { Net: "unix", }) if err != nil { - return E.Cause(err, "listen") + return E.Cause(err, "listen ", sockPath) } if sUserID > 0 { err = os.Chown(sockPath, sUserID, sGroupID) diff --git a/experimental/libbox/profile_import.go b/experimental/libbox/profile_import.go index 7b2b7cb6..cbe72907 100644 --- a/experimental/libbox/profile_import.go +++ b/experimental/libbox/profile_import.go @@ -2,7 +2,9 @@ package libbox import ( "bytes" + "compress/gzip" "encoding/binary" + "io" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/rw" @@ -170,19 +172,25 @@ type ProfileContent struct { } func (c *ProfileContent) Encode() []byte { - var buffer bytes.Buffer + buffer := new(bytes.Buffer) buffer.WriteByte(MessageTypeProfileContent) - rw.WriteVString(&buffer, c.Name) - binary.Write(&buffer, binary.BigEndian, c.Type) - rw.WriteVString(&buffer, c.Config) - rw.WriteVString(&buffer, c.RemotePath) - binary.Write(&buffer, binary.BigEndian, c.AutoUpdate) - binary.Write(&buffer, binary.BigEndian, c.LastUpdated) + buffer.WriteByte(0) + writer := gzip.NewWriter(buffer) + rw.WriteVString(writer, c.Name) + binary.Write(writer, binary.BigEndian, c.Type) + rw.WriteVString(writer, c.Config) + if c.Type != ProfileTypeLocal { + rw.WriteVString(writer, c.RemotePath) + binary.Write(writer, binary.BigEndian, c.AutoUpdate) + binary.Write(writer, binary.BigEndian, c.LastUpdated) + } + writer.Flush() + writer.Close() return buffer.Bytes() } func DecodeProfileContent(data []byte) (*ProfileContent, error) { - reader := bytes.NewReader(data) + var reader io.Reader = bytes.NewReader(data) messageType, err := rw.ReadByte(reader) if err != nil { return nil, err @@ -190,6 +198,18 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { if messageType != MessageTypeProfileContent { return nil, E.New("invalid message") } + version, err := rw.ReadByte(reader) + if err != nil { + return nil, err + } + if version == 0 { + reader, err = gzip.NewReader(reader) + if err != nil { + return nil, E.Cause(err, "unsupported profile") + } + } else { + return nil, E.Cause(err, "unsupported profile") + } var content ProfileContent content.Name, err = rw.ReadVString(reader) if err != nil { @@ -203,17 +223,19 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { if err != nil { return nil, err } - content.RemotePath, err = rw.ReadVString(reader) - if err != nil { - return nil, err - } - err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate) - if err != nil { - return nil, err - } - err = binary.Read(reader, binary.BigEndian, &content.LastUpdated) - if err != nil { - return nil, err + if content.Type != ProfileTypeLocal { + content.RemotePath, err = rw.ReadVString(reader) + if err != nil { + return nil, err + } + err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate) + if err != nil { + return nil, err + } + err = binary.Read(reader, binary.BigEndian, &content.LastUpdated) + if err != nil { + return nil, err + } } return &content, nil } diff --git a/test/go.mod b/test/go.mod index 81d1cf02..6144a3ad 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,9 +10,9 @@ require ( github.com/docker/docker v20.10.18+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid/v5 v5.0.0 - github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7 - github.com/sagernet/sing-shadowsocks v0.2.3-0.20230703131347-b044960bd355 - github.com/sagernet/sing-shadowsocks2 v0.1.2-0.20230703131506-ca0c6adde968 + github.com/sagernet/sing v0.2.9 + github.com/sagernet/sing-shadowsocks v0.2.4 + github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.4 go.uber.org/goleak v1.2.0 @@ -26,7 +26,7 @@ require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect - github.com/caddyserver/certmagic v0.18.2 // indirect + github.com/caddyserver/certmagic v0.19.0 // indirect github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect github.com/cretz/bine v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -34,9 +34,9 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-chi/chi/v5 v5.0.8 // indirect + github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/go-chi/cors v1.2.1 // indirect - github.com/go-chi/render v1.0.2 // indirect + github.com/go-chi/render v1.0.3 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -73,11 +73,11 @@ require ( github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect - github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9 // indirect - github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90 // indirect - github.com/sagernet/sing-shadowtls v0.1.3-0.20230703132509-93bbad3057e4 // indirect - github.com/sagernet/sing-tun v0.1.9-0.20230703134424-fd850d00e5cd // indirect - github.com/sagernet/sing-vmess v0.1.7-0.20230711074224-7d2a9a318162 // indirect + github.com/sagernet/sing-dns v0.1.8 // indirect + github.com/sagernet/sing-mux v0.1.2 // indirect + github.com/sagernet/sing-shadowtls v0.1.4 // indirect + github.com/sagernet/sing-tun v0.1.11 // indirect + github.com/sagernet/sing-vmess v0.1.7 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect diff --git a/test/go.sum b/test/go.sum index 98ee8193..63328a2a 100644 --- a/test/go.sum +++ b/test/go.sum @@ -14,6 +14,7 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/caddyserver/certmagic v0.18.2 h1:Nj2+M+A2Ho9IF6n1wUSbra4mX1X6ALzWpul9HooprHA= github.com/caddyserver/certmagic v0.18.2/go.mod h1:cLsgYXecH1iVUPjDXw15/1SKjZk/TK+aFfQk5FnugGQ= +github.com/caddyserver/certmagic v0.19.0/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -39,10 +40,12 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= @@ -132,20 +135,28 @@ github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2 github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7 h1:sKQu4Gc6vMfzleoFK3h8ROfn/TP2U36qQME3/iYaoqA= github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= +github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9 h1:35qe74ygIKj5uQkDDD0Xtv+iWOspQsS/Lqhs2XiY0Ak= github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9/go.mod h1:wgmbh0yruJXRO8tmfwPx6hOl6pyReWRoeHdkRehWkmw= +github.com/sagernet/sing-dns v0.1.8/go.mod h1:lHv8WMl9GKfMV8Wt1AJTtjVTF/h5/owpGY2YutYZF6g= github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90 h1:aEe2HrRc9OTS7IZ8RHyh224OhltnwRQs4/y89UsHPo8= github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90/go.mod h1:sm126rB5EUi9HLf4jCSHTqo+XRPbh4BoEVeLbr2WRbE= +github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= github.com/sagernet/sing-shadowsocks v0.2.3-0.20230703131347-b044960bd355 h1:XOgNZYnkDrx5qtNS4kqIOHMhjZuc7mJ2pY/x3EyZX8Q= github.com/sagernet/sing-shadowsocks v0.2.3-0.20230703131347-b044960bd355/go.mod h1:atEATsxqPo8qCPcFt8Rw7TFEJ70egCoMR7PziX4jmjI= +github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM= github.com/sagernet/sing-shadowsocks2 v0.1.2-0.20230703131506-ca0c6adde968 h1:UctXygnZfqsFR+2hZXfpWK3pSYKLbBQMuli9GDE6QU0= github.com/sagernet/sing-shadowsocks2 v0.1.2-0.20230703131506-ca0c6adde968/go.mod h1:xFxUGbtnqRLxtQftCILFeKf43GE6S83f0I6CsO9BxGE= +github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw= github.com/sagernet/sing-shadowtls v0.1.3-0.20230703132509-93bbad3057e4 h1:ZjLyCkEENqXzGp4PRZbQGk5wPzEq0Rg+/2jK82lmy3Q= github.com/sagernet/sing-shadowtls v0.1.3-0.20230703132509-93bbad3057e4/go.mod h1:8ZSSHJSNOG7cUCUYJemZNH873EsKdFqABykTypoS/2M= +github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-tun v0.1.9-0.20230703134424-fd850d00e5cd h1:lkJA/P1L2XE5lNDnzA2fygx6DZIks3Sx87GN2OE0jNY= github.com/sagernet/sing-tun v0.1.9-0.20230703134424-fd850d00e5cd/go.mod h1:XNQoXtvsmeva+dADmo/57KktLNgm5ubOyR67Niahqj8= +github.com/sagernet/sing-tun v0.1.11/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= github.com/sagernet/sing-vmess v0.1.7-0.20230711074224-7d2a9a318162 h1:EvEbqAdZJalFqLwKSHIixH+TRed0iY2f8KatI7mtt1Q= github.com/sagernet/sing-vmess v0.1.7-0.20230711074224-7d2a9a318162/go.mod h1:FHEBboQRvfBDbZd/fAKBpgR4w7QbYyzNL/MLd22/iTk= +github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0= github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE= From 4fcce1f073bc90b66edc71dd3e3936f672c3fbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 31 Jul 2023 09:04:55 +0800 Subject: [PATCH 02/20] Fix http proxy server --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2ce33680..22acc2a5 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.2.9 + github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda github.com/sagernet/sing-dns v0.1.8 github.com/sagernet/sing-mux v0.1.2 github.com/sagernet/sing-shadowsocks v0.2.4 diff --git a/go.sum b/go.sum index 265fe540..3a8899a3 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.9 h1:3wsTz+JG5Wzy65eZnh6AuCrD2QqcRF6Iq6f7ttmJsAo= -github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= +github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda h1:gTIL4AFP/jFXChtjWBJ6i9SaCUZP4BiL628i15El7QI= +github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.8 h1:zTYnxzA7mssg/Lwd70+RFPi8i3djioGnVS7zKwSF6cg= github.com/sagernet/sing-dns v0.1.8/go.mod h1:lHv8WMl9GKfMV8Wt1AJTtjVTF/h5/owpGY2YutYZF6g= github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g= From 679739683e327a9a173a9b4e687517e5c674b873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 31 Jul 2023 09:07:32 +0800 Subject: [PATCH 03/20] Update dependencies --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 22acc2a5..a6de59e5 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( berty.tech/go-libtor v1.0.385 github.com/Dreamacro/clash v1.17.0 - github.com/caddyserver/certmagic v0.19.0 + github.com/caddyserver/certmagic v0.19.1 github.com/cretz/bine v0.2.0 github.com/dustin/go-humanize v1.0.1 github.com/fsnotify/fsnotify v1.6.0 @@ -13,7 +13,7 @@ require ( github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.0.0 - github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df + github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.55 @@ -42,13 +42,13 @@ require ( github.com/stretchr/testify v1.8.4 go.etcd.io/bbolt v1.3.7 go.uber.org/zap v1.24.0 - go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 + go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 golang.org/x/crypto v0.11.0 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df golang.org/x/net v0.12.0 golang.org/x/sys v0.10.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 - google.golang.org/grpc v1.56.2 + google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 ) @@ -93,7 +93,7 @@ require ( golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.10.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/go.sum b/go.sum index 3a8899a3..ce9eeeb3 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/caddyserver/certmagic v0.19.0 h1:HuJ1Yf1H1jAfmBGrSSQN1XRkafnWcpDtyIiyMV6vmpM= -github.com/caddyserver/certmagic v0.19.0/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= +github.com/caddyserver/certmagic v0.19.1 h1:4jyOYm2DHvQI8YM0sk6qm62Gl5XznHxiMBMWjMTlQkw= +github.com/caddyserver/certmagic v0.19.1/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -54,8 +54,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4= -github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= +github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd h1:D772X7igTag7yKErVWAR7boXpOml3fqqBzH1wNaD/jk= +github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -174,8 +174,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo= -go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= +go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 h1:zLxFnORHDFTSkJPawMU7LzsuGQJ4MUFS653jJHpORow= +go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= @@ -215,10 +215,10 @@ golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= From f57bee2f4b9289feb92b267f722950aa9274c6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 23 Jul 2023 14:42:19 +0800 Subject: [PATCH 04/20] Update quic-go --- go.mod | 8 ++++---- go.sum | 22 +++++++++++----------- test/go.mod | 20 ++++++++++---------- test/go.sum | 11 +++++++++++ transport/hysteria/brutal.go | 4 ++-- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index a6de59e5..df642d5c 100644 --- a/go.mod +++ b/go.mod @@ -23,10 +23,10 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59 github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 - github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 + github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda - github.com/sagernet/sing-dns v0.1.8 + github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 github.com/sagernet/sing-mux v0.1.2 github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 @@ -61,7 +61,7 @@ require ( github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect @@ -72,7 +72,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/libdns/libdns v0.2.1 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/onsi/ginkgo/v2 v2.2.0 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index ce9eeeb3..ca008232 100644 --- a/go.sum +++ b/go.sum @@ -33,10 +33,11 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -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/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -77,9 +78,9 @@ github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= -github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= @@ -111,16 +112,16 @@ github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTS github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 h1:9S+L1n/4hbe1pCLNTZnnddSNseQda8tuSm/+uRy6p8s= -github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02/go.mod h1:rth94YcHJfkC4mG03JTXmv7mJsDc8MOIIqQrCtoaV4U= +github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 h1:BjZBIgwzlIbRSijdGQYZf0CaqHY1ZEIOUqVEKEICU0U= +github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111/go.mod h1:5rilP6WxqIl/4ypZbMjr+MK+STxuCEvO5yVtEyYNZ6g= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda h1:gTIL4AFP/jFXChtjWBJ6i9SaCUZP4BiL628i15El7QI= github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= -github.com/sagernet/sing-dns v0.1.8 h1:zTYnxzA7mssg/Lwd70+RFPi8i3djioGnVS7zKwSF6cg= -github.com/sagernet/sing-dns v0.1.8/go.mod h1:lHv8WMl9GKfMV8Wt1AJTtjVTF/h5/owpGY2YutYZF6g= +github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= +github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g= github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY= @@ -151,7 +152,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -226,7 +227,6 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/go.mod b/test/go.mod index 6144a3ad..cfd00cc8 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v20.10.18+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid/v5 v5.0.0 - github.com/sagernet/sing v0.2.9 + github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/spyzhov/ajson v0.7.1 @@ -26,7 +26,7 @@ require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect - github.com/caddyserver/certmagic v0.19.0 // indirect + github.com/caddyserver/certmagic v0.19.1 // indirect github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect github.com/cretz/bine v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -38,13 +38,13 @@ require ( github.com/go-chi/cors v1.2.1 // indirect github.com/go-chi/render v1.0.3 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df // indirect + github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd // indirect github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect @@ -54,7 +54,7 @@ require ( github.com/miekg/dns v1.1.55 // indirect github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/onsi/ginkgo/v2 v2.2.0 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/ooni/go-libtor v1.1.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect @@ -71,9 +71,9 @@ require ( github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect - github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 // indirect + github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect - github.com/sagernet/sing-dns v0.1.8 // indirect + github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 // indirect github.com/sagernet/sing-mux v0.1.2 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-tun v0.1.11 // indirect @@ -91,7 +91,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 // indirect + go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.11.0 // indirect @@ -99,8 +99,8 @@ require ( golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.10.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.56.2 // indirect + google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect + google.golang.org/grpc v1.57.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.4.0 // indirect diff --git a/test/go.sum b/test/go.sum index 63328a2a..47bec557 100644 --- a/test/go.sum +++ b/test/go.sum @@ -15,6 +15,7 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/caddyserver/certmagic v0.18.2 h1:Nj2+M+A2Ho9IF6n1wUSbra4mX1X6ALzWpul9HooprHA= github.com/caddyserver/certmagic v0.18.2/go.mod h1:cLsgYXecH1iVUPjDXw15/1SKjZk/TK+aFfQk5FnugGQ= github.com/caddyserver/certmagic v0.19.0/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= +github.com/caddyserver/certmagic v0.19.1/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -50,6 +51,7 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= 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/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -69,6 +71,7 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4= github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= +github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -94,6 +97,7 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= @@ -129,6 +133,7 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 h1:9S+L1n/4hbe1pCLNTZnnddSNseQda8tuSm/+uRy6p8s= github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02/go.mod h1:rth94YcHJfkC4mG03JTXmv7mJsDc8MOIIqQrCtoaV4U= +github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111/go.mod h1:5rilP6WxqIl/4ypZbMjr+MK+STxuCEvO5yVtEyYNZ6g= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= @@ -136,9 +141,11 @@ github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuD github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7 h1:sKQu4Gc6vMfzleoFK3h8ROfn/TP2U36qQME3/iYaoqA= github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= +github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9 h1:35qe74ygIKj5uQkDDD0Xtv+iWOspQsS/Lqhs2XiY0Ak= github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9/go.mod h1:wgmbh0yruJXRO8tmfwPx6hOl6pyReWRoeHdkRehWkmw= github.com/sagernet/sing-dns v0.1.8/go.mod h1:lHv8WMl9GKfMV8Wt1AJTtjVTF/h5/owpGY2YutYZF6g= +github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90 h1:aEe2HrRc9OTS7IZ8RHyh224OhltnwRQs4/y89UsHPo8= github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90/go.mod h1:sm126rB5EUi9HLf4jCSHTqo+XRPbh4BoEVeLbr2WRbE= github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= @@ -176,6 +183,7 @@ github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzy github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -197,6 +205,7 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo= go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= +go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -262,8 +271,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= diff --git a/transport/hysteria/brutal.go b/transport/hysteria/brutal.go index 0e6dc794..44780dab 100644 --- a/transport/hysteria/brutal.go +++ b/transport/hysteria/brutal.go @@ -50,8 +50,8 @@ func (b *BrutalSender) TimeUntilSend(bytesInFlight congestion.ByteCount) time.Ti return b.pacer.TimeUntilSend() } -func (b *BrutalSender) HasPacingBudget() bool { - return b.pacer.Budget(time.Now()) >= b.maxDatagramSize +func (b *BrutalSender) HasPacingBudget(now time.Time) bool { + return b.pacer.Budget(now) >= b.maxDatagramSize } func (b *BrutalSender) CanSend(bytesInFlight congestion.ByteCount) bool { From 8d629ef323b1b9bd50d339b9de02c12b56c5b965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 31 Jul 2023 09:41:42 +0800 Subject: [PATCH 05/20] documentation: Update changelog --- docs/changelog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 47ca4514..17775ff2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,14 @@ +#### 1.3.5 + +* Fixes and improvements +* Introducing our [Apple tvOS](/installation/clients/sft) client applications **1** +* Add per app proxy and app installed/updated trigger support for Android client +* Add profile sharing support for Android/iOS/macOS clients + +**1**: + +Due to the requirement of tvOS 17, the app cannot be submitted to the App Store for the time being, and can only be downloaded through TestFlight. + #### 1.3.4 * Fixes and improvements From 1983f549076d5764844389d219d323cf266716f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 1 Aug 2023 16:40:11 +0800 Subject: [PATCH 06/20] platform: Add sleep support for NetworkExtension --- common/sleep/manager.go | 43 ++++++++++++++++++++++++++++++++++ experimental/libbox/service.go | 26 +++++++++++++++----- outbound/urltest.go | 42 +++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 common/sleep/manager.go diff --git a/common/sleep/manager.go b/common/sleep/manager.go new file mode 100644 index 00000000..a668e76f --- /dev/null +++ b/common/sleep/manager.go @@ -0,0 +1,43 @@ +package sleep + +import ( + "sync" +) + +type Manager struct { + access sync.Mutex + done chan struct{} +} + +func NewManager() *Manager { + closedChan := make(chan struct{}) + close(closedChan) + return &Manager{ + done: closedChan, + } +} + +func (m *Manager) Sleep() { + m.access.Lock() + defer m.access.Unlock() + select { + case <-m.done: + default: + return + } + m.done = make(chan struct{}) +} + +func (m *Manager) Wake() { + m.access.Lock() + defer m.access.Unlock() + select { + case <-m.done: + default: + close(m.done) + } +} + +func (m *Manager) Active() <-chan struct{} { + return m.done +} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index f37f8408..0b63659e 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -8,6 +8,7 @@ import ( "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/common/sleep" "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" "github.com/sagernet/sing-box/experimental/libbox/platform" @@ -22,9 +23,10 @@ import ( ) type BoxService struct { - ctx context.Context - cancel context.CancelFunc - instance *box.Box + ctx context.Context + cancel context.CancelFunc + instance *box.Box + sleepManager *sleep.Manager } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { @@ -35,6 +37,8 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx, cancel := context.WithCancel(context.Background()) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage()) + sleepManager := sleep.NewManager() + ctx = service.ContextWithPtr(ctx, sleepManager) instance, err := box.New(box.Options{ Context: ctx, Options: options, @@ -45,9 +49,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box return nil, E.Cause(err, "create service") } return &BoxService{ - ctx: ctx, - cancel: cancel, - instance: instance, + ctx: ctx, + cancel: cancel, + instance: instance, + sleepManager: sleepManager, }, nil } @@ -60,6 +65,15 @@ func (s *BoxService) Close() error { return s.instance.Close() } +func (s *BoxService) Sleep() { + s.sleepManager.Sleep() + _ = s.instance.Router().ResetNetwork() +} + +func (s *BoxService) Wake() { + s.sleepManager.Wake() +} + var _ platform.Interface = (*platformInterfaceWrapper)(nil) type platformInterfaceWrapper struct { diff --git a/outbound/urltest.go b/outbound/urltest.go index fce7bd05..1c95084d 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -8,6 +8,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sleep" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -143,15 +144,16 @@ func (s *URLTest) InterfaceUpdated() error { } type URLTestGroup struct { - ctx context.Context - router adapter.Router - logger log.Logger - outbounds []adapter.Outbound - link string - interval time.Duration - tolerance uint16 - history *urltest.HistoryStorage - checking atomic.Bool + ctx context.Context + router adapter.Router + logger log.Logger + outbounds []adapter.Outbound + link string + interval time.Duration + tolerance uint16 + history *urltest.HistoryStorage + checking atomic.Bool + sleepManager *sleep.Manager access sync.Mutex ticker *time.Ticker @@ -173,15 +175,16 @@ func NewURLTestGroup(ctx context.Context, router adapter.Router, logger log.Logg history = urltest.NewHistoryStorage() } return &URLTestGroup{ - ctx: ctx, - router: router, - logger: logger, - outbounds: outbounds, - link: link, - interval: interval, - tolerance: tolerance, - history: history, - close: make(chan struct{}), + ctx: ctx, + router: router, + logger: logger, + outbounds: outbounds, + link: link, + interval: interval, + tolerance: tolerance, + history: history, + close: make(chan struct{}), + sleepManager: service.PtrFromContext[sleep.Manager](ctx), } } @@ -263,6 +266,9 @@ func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound { func (g *URLTestGroup) loopCheck() { go g.CheckOutbounds(true) for { + if g.sleepManager != nil { + <-g.sleepManager.Active() + } select { case <-g.close: return From 2123b216c04c651b7cb37d22a8e226434eaa476c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 2 Aug 2023 19:00:22 +0800 Subject: [PATCH 07/20] Fix http proxy server again --- go.mod | 2 +- go.sum | 4 ++-- test/go.mod | 2 +- test/go.sum | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index df642d5c..f80a556f 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda + github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 github.com/sagernet/sing-mux v0.1.2 github.com/sagernet/sing-shadowsocks v0.2.4 diff --git a/go.sum b/go.sum index ca008232..44c62112 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda h1:gTIL4AFP/jFXChtjWBJ6i9SaCUZP4BiL628i15El7QI= -github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee h1:5MATgtWMh2TCAVMtQnC3UcVMympANU7zXEekctD29PY= +github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g= diff --git a/test/go.mod b/test/go.mod index cfd00cc8..8cf94321 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v20.10.18+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid/v5 v5.0.0 - github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda + github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/spyzhov/ajson v0.7.1 diff --git a/test/go.sum b/test/go.sum index 47bec557..ae31ba7d 100644 --- a/test/go.sum +++ b/test/go.sum @@ -142,6 +142,7 @@ github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7 h1:sKQu4Gc6vMfzleo github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9 h1:35qe74ygIKj5uQkDDD0Xtv+iWOspQsS/Lqhs2XiY0Ak= github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9/go.mod h1:wgmbh0yruJXRO8tmfwPx6hOl6pyReWRoeHdkRehWkmw= github.com/sagernet/sing-dns v0.1.8/go.mod h1:lHv8WMl9GKfMV8Wt1AJTtjVTF/h5/owpGY2YutYZF6g= From c40140bbae2f2bbb3fd33161e60c5c118ff9eaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 3 Aug 2023 15:03:29 +0800 Subject: [PATCH 08/20] Fix UDP async write --- go.mod | 4 ++-- go.sum | 8 ++++---- test/go.mod | 4 ++-- test/go.sum | 2 ++ transport/trojan/protocol.go | 14 +++++++++++--- transport/vless/client.go | 28 +++++++++++++++++++++------- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index f80a556f..9239496f 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 - github.com/sagernet/sing-mux v0.1.2 + github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/sagernet/sing-shadowtls v0.1.4 @@ -45,7 +45,7 @@ require ( go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 golang.org/x/crypto v0.11.0 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df - golang.org/x/net v0.12.0 + golang.org/x/net v0.13.0 golang.org/x/sys v0.10.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.57.0 diff --git a/go.sum b/go.sum index 44c62112..3ca3b15a 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee h1:5MATgtWMh2TCAV github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= -github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g= -github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= +github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 h1:IQ7oBBKz+lwIqwI9IMStlQ9YSUu3eKJmNTip0aLbvOI= +github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY= github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM= github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbsBM6Fwbl5kGA= @@ -187,8 +187,8 @@ golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/test/go.mod b/test/go.mod index 8cf94321..d4e42fbb 100644 --- a/test/go.mod +++ b/test/go.mod @@ -16,7 +16,7 @@ require ( github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.4 go.uber.org/goleak v1.2.0 - golang.org/x/net v0.12.0 + golang.org/x/net v0.13.0 ) require ( @@ -74,7 +74,7 @@ require ( github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 // indirect - github.com/sagernet/sing-mux v0.1.2 // indirect + github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-tun v0.1.11 // indirect github.com/sagernet/sing-vmess v0.1.7 // indirect diff --git a/test/go.sum b/test/go.sum index ae31ba7d..766545db 100644 --- a/test/go.sum +++ b/test/go.sum @@ -150,6 +150,7 @@ github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GH github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90 h1:aEe2HrRc9OTS7IZ8RHyh224OhltnwRQs4/y89UsHPo8= github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90/go.mod h1:sm126rB5EUi9HLf4jCSHTqo+XRPbh4BoEVeLbr2WRbE= github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= +github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= github.com/sagernet/sing-shadowsocks v0.2.3-0.20230703131347-b044960bd355 h1:XOgNZYnkDrx5qtNS4kqIOHMhjZuc7mJ2pY/x3EyZX8Q= github.com/sagernet/sing-shadowsocks v0.2.3-0.20230703131347-b044960bd355/go.mod h1:atEATsxqPo8qCPcFt8Rw7TFEJ70egCoMR7PziX4jmjI= github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM= @@ -229,6 +230,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/transport/trojan/protocol.go b/transport/trojan/protocol.go index b81b9046..b208c084 100644 --- a/transport/trojan/protocol.go +++ b/transport/trojan/protocol.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "net" "os" + "sync" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -84,6 +85,7 @@ func (c *ClientConn) Upstream() any { type ClientPacketConn struct { net.Conn + access sync.Mutex key [KeyLength]byte headerWritten bool } @@ -105,9 +107,15 @@ func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { if !c.headerWritten { - err := ClientHandshakePacket(c.Conn, c.key, destination, buffer) - c.headerWritten = true - return err + c.access.Lock() + if c.headerWritten { + c.access.Unlock() + } else { + err := ClientHandshakePacket(c.Conn, c.key, destination, buffer) + c.headerWritten = true + c.access.Unlock() + return err + } } return WritePacket(c.Conn, buffer, destination) } diff --git a/transport/vless/client.go b/transport/vless/client.go index 14a4e4c8..cb9ca9cb 100644 --- a/transport/vless/client.go +++ b/transport/vless/client.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "io" "net" + "sync" "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" @@ -190,6 +191,7 @@ func (c *Conn) Upstream() any { type PacketConn struct { net.Conn + access sync.Mutex key [16]byte destination M.Socksaddr flow string @@ -218,11 +220,17 @@ func (c *PacketConn) Read(b []byte) (n int, err error) { func (c *PacketConn) Write(b []byte) (n int, err error) { if !c.requestWritten { - err = WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, nil) - if err == nil { - n = len(b) + c.access.Lock() + if c.requestWritten { + c.access.Unlock() + } else { + err = WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, nil) + if err == nil { + n = len(b) + } + c.requestWritten = true + c.access.Unlock() } - c.requestWritten = true } err = binary.Write(c.Conn, binary.BigEndian, uint16(len(b))) if err != nil { @@ -236,9 +244,15 @@ func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) er dataLen := buffer.Len() binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(dataLen)) if !c.requestWritten { - err := WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, buffer.Bytes()) - c.requestWritten = true - return err + c.access.Lock() + if c.requestWritten { + c.access.Unlock() + } else { + err := WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination, c.flow}, buffer.Bytes()) + c.requestWritten = true + c.access.Unlock() + return err + } } return common.Error(c.Conn.Write(buffer.Bytes())) } From 59987747e52918315d57e2f4b40b61e9cb4c21fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 4 Aug 2023 17:13:46 +0800 Subject: [PATCH 09/20] platform: Improve status --- constant/proxy.go | 45 +++++++++++++++++++++++++++- experimental/clashapi/proxies.go | 30 +------------------ experimental/libbox/command_group.go | 6 ++++ experimental/libbox/setup.go | 4 +++ inbound/builder.go | 2 +- inbound/socks.go | 2 +- option/inbound.go | 4 +-- option/outbound.go | 4 +-- outbound/builder.go | 2 +- outbound/socks.go | 2 +- test/vless_test.go | 2 +- 11 files changed, 64 insertions(+), 39 deletions(-) diff --git a/constant/proxy.go b/constant/proxy.go index f875d7e0..7fc12d36 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -7,7 +7,7 @@ const ( TypeDirect = "direct" TypeBlock = "block" TypeDNS = "dns" - TypeSocks = "socks" + TypeSOCKS = "socks" TypeHTTP = "http" TypeMixed = "mixed" TypeShadowsocks = "shadowsocks" @@ -27,3 +27,46 @@ const ( TypeSelector = "selector" TypeURLTest = "urltest" ) + +func ProxyDisplayName(proxyType string) string { + switch proxyType { + case TypeDirect: + return "Direct" + case TypeBlock: + return "Block" + case TypeDNS: + return "DNS" + case TypeSOCKS: + return "SOCKS" + case TypeHTTP: + return "HTTP" + case TypeShadowsocks: + return "Shadowsocks" + case TypeVMess: + return "VMess" + case TypeTrojan: + return "Trojan" + case TypeNaive: + return "Naive" + case TypeWireGuard: + return "WireGuard" + case TypeHysteria: + return "Hysteria" + case TypeTor: + return "Tor" + case TypeSSH: + return "SSH" + case TypeShadowTLS: + return "ShadowTLS" + case TypeShadowsocksR: + return "ShadowsocksR" + case TypeVLESS: + return "VLESS" + case TypeSelector: + return "Selector" + case TypeURLTest: + return "URLTest" + default: + return "Unknown" + } +} diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 8e3e19f4..f312481c 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -63,38 +63,10 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject { var info badjson.JSONObject var clashType string switch detour.Type() { - case C.TypeDirect: - clashType = "Direct" case C.TypeBlock: clashType = "Reject" - case C.TypeSocks: - clashType = "Socks" - case C.TypeHTTP: - clashType = "HTTP" - case C.TypeShadowsocks: - clashType = "Shadowsocks" - case C.TypeVMess: - clashType = "VMess" - case C.TypeTrojan: - clashType = "Trojan" - case C.TypeHysteria: - clashType = "Hysteria" - case C.TypeWireGuard: - clashType = "WireGuard" - case C.TypeShadowsocksR: - clashType = "ShadowsocksR" - case C.TypeVLESS: - clashType = "VLESS" - case C.TypeTor: - clashType = "Tor" - case C.TypeSSH: - clashType = "SSH" - case C.TypeSelector: - clashType = "Selector" - case C.TypeURLTest: - clashType = "URLTest" default: - clashType = "Direct" + clashType = C.ProxyDisplayName(detour.Type()) } info.Put("type", clashType) info.Put("name", detour.Tag()) diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index 6ff8770e..fef043cb 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "io" "net" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" @@ -71,6 +72,11 @@ func (s *CommandServer) handleGroupConn(conn net.Conn) error { } } select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(2 * time.Second): + } + select { case <-ctx.Done(): return ctx.Err() case <-s.urlTestUpdate: diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index 2348a03a..e028151f 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -48,3 +48,7 @@ func Version() string { func FormatBytes(length int64) string { return humanize.IBytes(uint64(length)) } + +func ProxyDisplayType(proxyType string) string { + return C.ProxyDisplayName(proxyType) +} diff --git a/inbound/builder.go b/inbound/builder.go index d62225a1..5243e6b8 100644 --- a/inbound/builder.go +++ b/inbound/builder.go @@ -24,7 +24,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil case C.TypeDirect: return NewDirect(ctx, router, logger, options.Tag, options.DirectOptions), nil - case C.TypeSocks: + case C.TypeSOCKS: return NewSocks(ctx, router, logger, options.Tag, options.SocksOptions), nil case C.TypeHTTP: return NewHTTP(ctx, router, logger, options.Tag, options.HTTPOptions) diff --git a/inbound/socks.go b/inbound/socks.go index 7471341f..8cc256ef 100644 --- a/inbound/socks.go +++ b/inbound/socks.go @@ -27,7 +27,7 @@ type Socks struct { func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks { inbound := &Socks{ myInboundAdapter{ - protocol: C.TypeSocks, + protocol: C.TypeSOCKS, network: []string{N.NetworkTCP}, ctx: ctx, router: router, diff --git a/option/inbound.go b/option/inbound.go index 9533532b..ef56be90 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -38,7 +38,7 @@ func (h Inbound) MarshalJSON() ([]byte, error) { v = h.TProxyOptions case C.TypeDirect: v = h.DirectOptions - case C.TypeSocks: + case C.TypeSOCKS: v = h.SocksOptions case C.TypeHTTP: v = h.HTTPOptions @@ -79,7 +79,7 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { v = &h.TProxyOptions case C.TypeDirect: v = &h.DirectOptions - case C.TypeSocks: + case C.TypeSOCKS: v = &h.SocksOptions case C.TypeHTTP: v = &h.HTTPOptions diff --git a/option/outbound.go b/option/outbound.go index 6a394b74..5b8eb936 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -36,7 +36,7 @@ func (h Outbound) MarshalJSON() ([]byte, error) { v = h.DirectOptions case C.TypeBlock, C.TypeDNS: v = nil - case C.TypeSocks: + case C.TypeSOCKS: v = h.SocksOptions case C.TypeHTTP: v = h.HTTPOptions @@ -81,7 +81,7 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error { v = &h.DirectOptions case C.TypeBlock, C.TypeDNS: v = nil - case C.TypeSocks: + case C.TypeSOCKS: v = &h.SocksOptions case C.TypeHTTP: v = &h.HTTPOptions diff --git a/outbound/builder.go b/outbound/builder.go index 45d48425..f32c5de6 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -27,7 +27,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, t return NewBlock(logger, tag), nil case C.TypeDNS: return NewDNS(router, tag), nil - case C.TypeSocks: + case C.TypeSOCKS: return NewSocks(router, logger, tag, options.SocksOptions) case C.TypeHTTP: return NewHTTP(router, logger, tag, options.HTTPOptions) diff --git a/outbound/socks.go b/outbound/socks.go index f314c9be..97579fd3 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -39,7 +39,7 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio } outbound := &Socks{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSocks, + protocol: C.TypeSOCKS, network: options.Network.Build(), router: router, logger: logger, diff --git a/test/vless_test.go b/test/vless_test.go index a5f38052..7757bc11 100644 --- a/test/vless_test.go +++ b/test/vless_test.go @@ -491,7 +491,7 @@ func testVLESSXrayInbound(t *testing.T, flow string) { }, }, { - Type: C.TypeSocks, + Type: C.TypeSOCKS, Tag: "vless-out", SocksOptions: option.SocksOutboundOptions{ ServerOptions: option.ServerOptions{ From 9fba4f02b680330f254fccd419ff400568cf9ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Aug 2023 18:40:59 +0800 Subject: [PATCH 10/20] Update dependencies --- go.mod | 16 ++++---- go.sum | 38 +++++++++---------- test/go.mod | 18 ++++----- test/go.sum | 106 +++++++++++++++++++++------------------------------- 4 files changed, 76 insertions(+), 102 deletions(-) diff --git a/go.mod b/go.mod index 9239496f..3d5495d1 100644 --- a/go.mod +++ b/go.mod @@ -13,12 +13,12 @@ require ( github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.0.0 - github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd + github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.55 github.com/ooni/go-libtor v1.1.8 - github.com/oschwald/maxminddb-golang v1.11.0 + github.com/oschwald/maxminddb-golang v1.12.0 github.com/pires/go-proxyproto v0.7.0 github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59 @@ -41,12 +41,12 @@ require ( github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 go.etcd.io/bbolt v1.3.7 - go.uber.org/zap v1.24.0 + go.uber.org/zap v1.25.0 go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 - golang.org/x/crypto v0.11.0 + golang.org/x/crypto v0.12.0 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df - golang.org/x/net v0.13.0 - golang.org/x/sys v0.10.0 + golang.org/x/net v0.14.0 + golang.org/x/sys v0.11.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -74,7 +74,6 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-18 v0.2.0 // indirect @@ -87,10 +86,9 @@ require ( github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/zeebo/blake3 v0.2.3 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.11.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.10.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect diff --git a/go.sum b/go.sum index 3ca3b15a..6177326b 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,7 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/caddyserver/certmagic v0.19.1 h1:4jyOYm2DHvQI8YM0sk6qm62Gl5XznHxiMBMWjMTlQkw= github.com/caddyserver/certmagic v0.19.1/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -55,8 +55,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd h1:D772X7igTag7yKErVWAR7boXpOml3fqqBzH1wNaD/jk= -github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= +github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs= +github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -83,14 +83,12 @@ github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3Ro github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= -github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= -github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= @@ -168,27 +166,25 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 h1:zLxFnORHDFTSkJPawMU7LzsuGQJ4MUFS653jJHpORow= go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -200,14 +196,14 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= 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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/test/go.mod b/test/go.mod index d4e42fbb..ad76cd38 100644 --- a/test/go.mod +++ b/test/go.mod @@ -16,7 +16,7 @@ require ( github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.4 go.uber.org/goleak v1.2.0 - golang.org/x/net v0.13.0 + golang.org/x/net v0.14.0 ) require ( @@ -44,7 +44,7 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd // indirect + github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c // indirect github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect @@ -58,7 +58,7 @@ require ( github.com/ooni/go-libtor v1.1.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/oschwald/maxminddb-golang v1.11.0 // indirect + github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -87,19 +87,19 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect go.etcd.io/bbolt v1.3.7 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect + go.uber.org/zap v1.25.0 // indirect go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 // indirect - golang.org/x/crypto v0.11.0 // indirect + golang.org/x/crypto v0.12.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.11.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.10.0 // indirect - google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect google.golang.org/grpc v1.57.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/test/go.sum b/test/go.sum index 766545db..3ed47839 100644 --- a/test/go.sum +++ b/test/go.sum @@ -11,10 +11,8 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/caddyserver/certmagic v0.18.2 h1:Nj2+M+A2Ho9IF6n1wUSbra4mX1X6ALzWpul9HooprHA= -github.com/caddyserver/certmagic v0.18.2/go.mod h1:cLsgYXecH1iVUPjDXw15/1SKjZk/TK+aFfQk5FnugGQ= -github.com/caddyserver/certmagic v0.19.0/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/caddyserver/certmagic v0.19.1 h1:4jyOYm2DHvQI8YM0sk6qm62Gl5XznHxiMBMWjMTlQkw= github.com/caddyserver/certmagic v0.19.1/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -39,18 +37,16 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= -github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= -github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -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/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= @@ -69,9 +65,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4= -github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= -github.com/insomniacslk/dhcp v0.0.0-20230720093626-5648422c16cd/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= +github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs= +github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -79,6 +74,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -95,18 +91,17 @@ github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbD github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= -github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= -github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= @@ -131,40 +126,27 @@ github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTS github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 h1:9S+L1n/4hbe1pCLNTZnnddSNseQda8tuSm/+uRy6p8s= -github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02/go.mod h1:rth94YcHJfkC4mG03JTXmv7mJsDc8MOIIqQrCtoaV4U= +github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 h1:BjZBIgwzlIbRSijdGQYZf0CaqHY1ZEIOUqVEKEICU0U= github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111/go.mod h1:5rilP6WxqIl/4ypZbMjr+MK+STxuCEvO5yVtEyYNZ6g= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7 h1:sKQu4Gc6vMfzleoFK3h8ROfn/TP2U36qQME3/iYaoqA= -github.com/sagernet/sing v0.2.8-0.20230707055657-7c9b4d624da7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= -github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= -github.com/sagernet/sing v0.2.10-0.20230731010140-26d3f3d91bda/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee h1:5MATgtWMh2TCAVMtQnC3UcVMympANU7zXEekctD29PY= github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= -github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9 h1:35qe74ygIKj5uQkDDD0Xtv+iWOspQsS/Lqhs2XiY0Ak= -github.com/sagernet/sing-dns v0.1.7-0.20230703131656-fd65b6178bf9/go.mod h1:wgmbh0yruJXRO8tmfwPx6hOl6pyReWRoeHdkRehWkmw= -github.com/sagernet/sing-dns v0.1.8/go.mod h1:lHv8WMl9GKfMV8Wt1AJTtjVTF/h5/owpGY2YutYZF6g= +github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= -github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90 h1:aEe2HrRc9OTS7IZ8RHyh224OhltnwRQs4/y89UsHPo8= -github.com/sagernet/sing-mux v0.1.1-0.20230703132253-2cedde0fbc90/go.mod h1:sm126rB5EUi9HLf4jCSHTqo+XRPbh4BoEVeLbr2WRbE= -github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= +github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 h1:IQ7oBBKz+lwIqwI9IMStlQ9YSUu3eKJmNTip0aLbvOI= github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= -github.com/sagernet/sing-shadowsocks v0.2.3-0.20230703131347-b044960bd355 h1:XOgNZYnkDrx5qtNS4kqIOHMhjZuc7mJ2pY/x3EyZX8Q= -github.com/sagernet/sing-shadowsocks v0.2.3-0.20230703131347-b044960bd355/go.mod h1:atEATsxqPo8qCPcFt8Rw7TFEJ70egCoMR7PziX4jmjI= +github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY= github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM= -github.com/sagernet/sing-shadowsocks2 v0.1.2-0.20230703131506-ca0c6adde968 h1:UctXygnZfqsFR+2hZXfpWK3pSYKLbBQMuli9GDE6QU0= -github.com/sagernet/sing-shadowsocks2 v0.1.2-0.20230703131506-ca0c6adde968/go.mod h1:xFxUGbtnqRLxtQftCILFeKf43GE6S83f0I6CsO9BxGE= +github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbsBM6Fwbl5kGA= github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw= -github.com/sagernet/sing-shadowtls v0.1.3-0.20230703132509-93bbad3057e4 h1:ZjLyCkEENqXzGp4PRZbQGk5wPzEq0Rg+/2jK82lmy3Q= -github.com/sagernet/sing-shadowtls v0.1.3-0.20230703132509-93bbad3057e4/go.mod h1:8ZSSHJSNOG7cUCUYJemZNH873EsKdFqABykTypoS/2M= +github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.1.9-0.20230703134424-fd850d00e5cd h1:lkJA/P1L2XE5lNDnzA2fygx6DZIks3Sx87GN2OE0jNY= -github.com/sagernet/sing-tun v0.1.9-0.20230703134424-fd850d00e5cd/go.mod h1:XNQoXtvsmeva+dADmo/57KktLNgm5ubOyR67Niahqj8= +github.com/sagernet/sing-tun v0.1.11 h1:wUfRQZ4eHk8suHkGKEFxjV5uXl3tfZhPm/v14/4lHvk= github.com/sagernet/sing-tun v0.1.11/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= -github.com/sagernet/sing-vmess v0.1.7-0.20230711074224-7d2a9a318162 h1:EvEbqAdZJalFqLwKSHIixH+TRed0iY2f8KatI7mtt1Q= -github.com/sagernet/sing-vmess v0.1.7-0.20230711074224-7d2a9a318162/go.mod h1:FHEBboQRvfBDbZd/fAKBpgR4w7QbYyzNL/MLd22/iTk= +github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4= github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0= @@ -184,7 +166,6 @@ github.com/spyzhov/ajson v0.7.1 h1:1MDIlPc6x0zjNtpa7tDzRAyFAvRX+X8ZsvtYz5lZg6A= github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -195,26 +176,29 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695AP github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo= -go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 h1:zLxFnORHDFTSkJPawMU7LzsuGQJ4MUFS653jJHpORow= go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= @@ -228,9 +212,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -250,15 +233,15 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -272,11 +255,9 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -284,7 +265,6 @@ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 09ffa2c66e6276fda5fcf3c16da142444a984458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Aug 2023 21:36:46 +0800 Subject: [PATCH 11/20] documentation: Update changelog --- docs/changelog.md | 4 ++++ docs/installation/clients/sfa.md | 1 + docs/installation/clients/sfa.zh.md | 1 + 3 files changed, 6 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 17775ff2..6b2316b1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,7 @@ +#### 1.3.6 + +* Fixes and improvements + #### 1.3.5 * Fixes and improvements diff --git a/docs/installation/clients/sfa.md b/docs/installation/clients/sfa.md index fb958972..9ad88b00 100644 --- a/docs/installation/clients/sfa.md +++ b/docs/installation/clients/sfa.md @@ -9,6 +9,7 @@ Experimental Android client for sing-box. #### Download * [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest) +* [Github Releases](https://SagerNet/sing-box/releases) #### Note diff --git a/docs/installation/clients/sfa.zh.md b/docs/installation/clients/sfa.zh.md index eb02470d..1383576e 100644 --- a/docs/installation/clients/sfa.zh.md +++ b/docs/installation/clients/sfa.zh.md @@ -9,6 +9,7 @@ #### 下载 * [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest) +* [Github Releases](https://SagerNet/sing-box/releases) #### 注意事项 From 2675aff98afb2ba46e15339d400915c20e599d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 7 Aug 2023 20:08:11 +0800 Subject: [PATCH 12/20] Fix tag detect --- cmd/internal/build_shared/tag.go | 11 +++++++++-- common/badversion/version.go | 20 +++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cmd/internal/build_shared/tag.go b/cmd/internal/build_shared/tag.go index 9a376385..b1fecab4 100644 --- a/cmd/internal/build_shared/tag.go +++ b/cmd/internal/build_shared/tag.go @@ -1,6 +1,9 @@ package build_shared -import "github.com/sagernet/sing/common/shell" +import ( + "github.com/sagernet/sing-box/common/badversion" + "github.com/sagernet/sing/common/shell" +) func ReadTag() (string, error) { currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput() @@ -12,5 +15,9 @@ func ReadTag() (string, error) { return currentTag[1:], nil } shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput() - return currentTagRev[1:] + "-" + shortCommit, nil + version := badversion.Parse(currentTagRev[1:]) + if version.PreReleaseIdentifier == "" { + version.Patch++ + } + return version.String() + "-" + shortCommit, nil } diff --git a/common/badversion/version.go b/common/badversion/version.go index faf292fe..23168eca 100644 --- a/common/badversion/version.go +++ b/common/badversion/version.go @@ -11,6 +11,7 @@ type Version struct { Major int Minor int Patch int + Commit string PreReleaseIdentifier string PreReleaseVersion int } @@ -37,16 +38,21 @@ func (v Version) After(anotherVersion Version) bool { return false } if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" { - if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" { + if v.PreReleaseIdentifier == anotherVersion.PreReleaseIdentifier { + if v.PreReleaseVersion > anotherVersion.PreReleaseVersion { + return true + } else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion { + return false + } + } else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" { + return true + } else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" { + return false + } else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" { return true } else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" { return false } - if v.PreReleaseVersion > anotherVersion.PreReleaseVersion { - return true - } else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion { - return false - } } return false } @@ -95,7 +101,7 @@ func Parse(versionName string) (version Version) { version.PreReleaseIdentifier = "beta" version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:]) } else { - version.PreReleaseIdentifier = identifier + version.Commit = identifier } } } From 6ec8ff79b558c2d04f82f1cad7f394e4c1670289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 23 Jul 2023 14:42:19 +0800 Subject: [PATCH 13/20] Fix network monitor --- adapter/router.go | 2 +- common/settings/proxy_darwin.go | 15 +++----- common/tls/std_server.go | 2 +- experimental/libbox/monitor.go | 15 +++----- experimental/libbox/platform/interface.go | 4 +- experimental/libbox/service.go | 5 ++- go.mod | 4 +- go.sum | 8 ++-- option/types.go | 6 +-- outbound/hysteria.go | 4 +- outbound/shadowsocks.go | 4 +- outbound/ssh.go | 4 +- outbound/trojan.go | 4 +- outbound/urltest.go | 4 +- outbound/vless.go | 4 +- outbound/vmess.go | 4 +- outbound/wireguard.go | 4 +- route/router.go | 46 +++++++++++------------ test/go.mod | 4 +- test/go.sum | 8 ++-- transport/dhcp/server.go | 7 +++- 21 files changed, 78 insertions(+), 80 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index 3cf9e6d4..df74ee0a 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -85,5 +85,5 @@ type DNSRule interface { } type InterfaceUpdateListener interface { - InterfaceUpdated() error + InterfaceUpdated() } diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 595fb944..101ed096 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -20,10 +20,10 @@ type systemProxy struct { isMixed bool } -func (p *systemProxy) update(event int) error { +func (p *systemProxy) update(event int) { newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) if p.interfaceName == newInterfaceName { - return nil + return } if p.interfaceName != "" { _ = p.unset() @@ -31,7 +31,7 @@ func (p *systemProxy) update(event int) error { p.interfaceName = newInterfaceName interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) if err != nil { - return err + return } if p.isMixed { err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run() @@ -40,9 +40,9 @@ func (p *systemProxy) update(event int) error { err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run() } if err == nil { - err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run() + _ = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run() } - return err + return } func (p *systemProxy) unset() error { @@ -88,10 +88,7 @@ func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() er port: port, isMixed: isMixed, } - err := proxy.update(tun.EventInterfaceUpdate) - if err != nil { - return nil, err - } + proxy.update(tun.EventInterfaceUpdate) proxy.element = interfaceMonitor.RegisterCallback(proxy.update) return func() error { interfaceMonitor.UnregisterCallback(proxy.element) diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 2c875855..f2bf56eb 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -164,8 +164,8 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, var acmeService adapter.Service var err error if options.ACME != nil && len(options.ACME.Domain) > 0 { - tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME)) //nolint:staticcheck + tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME)) if err != nil { return nil, err } diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 685d6ccb..1cd29139 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -1,7 +1,6 @@ package libbox import ( - "context" "net" "net/netip" "sync" @@ -9,6 +8,7 @@ import ( "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/x/list" ) @@ -20,13 +20,13 @@ var ( type platformDefaultInterfaceMonitor struct { *platformInterfaceWrapper - errorHandler E.Handler networkAddresses []networkAddress defaultInterfaceName string defaultInterfaceIndex int element *list.Element[tun.NetworkUpdateCallback] access sync.Mutex callbacks list.List[tun.DefaultInterfaceUpdateCallback] + logger logger.Logger } type networkAddress struct { @@ -96,7 +96,7 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s err = m.router.UpdateInterfaces() } if err != nil { - m.errorHandler.NewError(context.Background(), E.Cause(err, "update interfaces")) + m.logger.Error(E.Cause(err, "update interfaces")) } interfaceIndex := int(interfaceIndex32) if interfaceName == "" { @@ -115,10 +115,10 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s } } if interfaceName == "" { - m.errorHandler.NewError(context.Background(), E.New("invalid interface name for ", interfaceIndex)) + m.logger.Error(E.New("invalid interface name for ", interfaceIndex)) return } else if interfaceIndex == -1 { - m.errorHandler.NewError(context.Background(), E.New("invalid interface index for ", interfaceName)) + m.logger.Error(E.New("invalid interface index for ", interfaceName)) return } if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex { @@ -130,10 +130,7 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s callbacks := m.callbacks.Array() m.access.Unlock() for _, callback := range callbacks { - err = callback(tun.EventInterfaceUpdate) - if err != nil { - m.errorHandler.NewError(context.Background(), err) - } + callback(tun.EventInterfaceUpdate) } } diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index e811bdb0..e99d842d 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -10,7 +10,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" ) type Interface interface { @@ -19,7 +19,7 @@ type Interface interface { AutoDetectInterfaceControl() control.Func OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) UsePlatformDefaultInterfaceMonitor() bool - CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor + CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor UsePlatformInterfaceGetter() bool Interfaces() ([]NetworkInterface, error) UnderNetworkExtension() bool diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 0b63659e..dc8c5caf 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -17,6 +17,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" @@ -158,11 +159,11 @@ func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { return w.iif.UsePlatformDefaultInterfaceMonitor() } -func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor { +func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, - errorHandler: errorHandler, defaultInterfaceIndex: -1, + logger: logger, } } diff --git a/go.mod b/go.mod index 3d5495d1..20a88a58 100644 --- a/go.mod +++ b/go.mod @@ -25,13 +25,13 @@ require ( github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee + github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.1.11 + github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 github.com/sagernet/sing-vmess v0.1.7 github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 diff --git a/go.sum b/go.sum index 6177326b..e593af1e 100644 --- a/go.sum +++ b/go.sum @@ -116,8 +116,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee h1:5MATgtWMh2TCAVMtQnC3UcVMympANU7zXEekctD29PY= -github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a h1:b89t6Mjgk4rJ5lrNMnCzy1/J116XkhgdB3YNd9FHyF4= +github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 h1:IQ7oBBKz+lwIqwI9IMStlQ9YSUu3eKJmNTip0aLbvOI= @@ -128,8 +128,8 @@ github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbs github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.1.11 h1:wUfRQZ4eHk8suHkGKEFxjV5uXl3tfZhPm/v14/4lHvk= -github.com/sagernet/sing-tun v0.1.11/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= +github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 h1:f1ejTKI6R+rQ2vHyD5pNHy0V+MhfBD1l8wiYyhTscnI= +github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4= github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= diff --git a/option/types.go b/option/types.go index f4dd4b48..8a5e6cfc 100644 --- a/option/types.go +++ b/option/types.go @@ -98,9 +98,9 @@ func (l *Listable[T]) UnmarshalJSON(content []byte) error { return nil } var singleItem T - err = json.Unmarshal(content, &singleItem) - if err != nil { - return err + newError := json.Unmarshal(content, &singleItem) + if newError != nil { + return E.Errors(err, newError) } *l = []T{singleItem} return nil diff --git a/outbound/hysteria.go b/outbound/hysteria.go index 207b9c4c..2c13ac6c 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -241,9 +241,9 @@ func (h *Hysteria) udpRecvLoop(conn quic.Connection) { } } -func (h *Hysteria) InterfaceUpdated() error { +func (h *Hysteria) InterfaceUpdated() { h.Close() - return nil + return } func (h *Hysteria) Close() error { diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index 79c18427..2f7d92cb 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -129,11 +129,11 @@ func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn return NewPacketConnection(ctx, h, conn, metadata) } -func (h *Shadowsocks) InterfaceUpdated() error { +func (h *Shadowsocks) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return nil + return } func (h *Shadowsocks) Close() error { diff --git a/outbound/ssh.go b/outbound/ssh.go index 4f434e78..93e5a7bc 100644 --- a/outbound/ssh.go +++ b/outbound/ssh.go @@ -174,9 +174,9 @@ func (s *SSH) connect() (*ssh.Client, error) { return client, nil } -func (s *SSH) InterfaceUpdated() error { +func (s *SSH) InterfaceUpdated() { common.Close(s.clientConn) - return nil + return } func (s *SSH) Close() error { diff --git a/outbound/trojan.go b/outbound/trojan.go index 3fc53171..a12041e4 100644 --- a/outbound/trojan.go +++ b/outbound/trojan.go @@ -104,11 +104,11 @@ func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, met return NewPacketConnection(ctx, h, conn, metadata) } -func (h *Trojan) InterfaceUpdated() error { +func (h *Trojan) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return nil + return } func (h *Trojan) Close() error { diff --git a/outbound/urltest.go b/outbound/urltest.go index 1c95084d..bd828e4b 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -138,9 +138,9 @@ func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, me return NewPacketConnection(ctx, s, conn, metadata) } -func (s *URLTest) InterfaceUpdated() error { +func (s *URLTest) InterfaceUpdated() { go s.group.CheckOutbounds(true) - return nil + return } type URLTestGroup struct { diff --git a/outbound/vless.go b/outbound/vless.go index 85f79fc6..12574467 100644 --- a/outbound/vless.go +++ b/outbound/vless.go @@ -123,11 +123,11 @@ func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, meta return NewPacketConnection(ctx, h, conn, metadata) } -func (h *VLESS) InterfaceUpdated() error { +func (h *VLESS) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return nil + return } func (h *VLESS) Close() error { diff --git a/outbound/vmess.go b/outbound/vmess.go index e4113854..6f7735fc 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -98,11 +98,11 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VMess) InterfaceUpdated() error { +func (h *VMess) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } - return nil + return } func (h *VMess) Close() error { diff --git a/outbound/wireguard.go b/outbound/wireguard.go index 4c2126c6..eb8f65db 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -186,9 +186,9 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (w *WireGuard) InterfaceUpdated() error { +func (w *WireGuard) InterfaceUpdated() { w.bind.Reset() - return nil + return } func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/route/router.go b/route/router.go index c23c8a45..568db59a 100644 --- a/route/router.go +++ b/route/router.go @@ -262,14 +262,16 @@ func NewRouter( if needInterfaceMonitor { if !usePlatformDefaultInterfaceMonitor { - networkMonitor, err := tun.NewNetworkUpdateMonitor(router) + networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) if err != os.ErrInvalid { if err != nil { return nil, err } router.networkMonitor = networkMonitor - networkMonitor.RegisterCallback(router.interfaceFinder.update) - interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, tun.DefaultInterfaceMonitorOptions{ + networkMonitor.RegisterCallback(func() { + _ = router.interfaceFinder.update() + }) + interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ OverrideAndroidVPN: options.OverrideAndroidVPN, UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), }) @@ -280,7 +282,7 @@ func NewRouter( router.interfaceMonitor = interfaceMonitor } } else { - interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router) + interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router.logger) interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) router.interfaceMonitor = interfaceMonitor } @@ -970,17 +972,21 @@ func (r *Router) NewError(ctx context.Context, err error) { r.logger.ErrorContext(ctx, err) } -func (r *Router) notifyNetworkUpdate(int) error { - if C.IsAndroid && r.platformInterface == nil { - var vpnStatus string - if r.interfaceMonitor.AndroidVPNEnabled() { - vpnStatus = "enabled" - } else { - vpnStatus = "disabled" - } - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) +func (r *Router) notifyNetworkUpdate(event int) { + if event == tun.EventNoRoute { + r.logger.Info("missing default interface") } else { - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + if C.IsAndroid && r.platformInterface == nil { + var vpnStatus string + if r.interfaceMonitor.AndroidVPNEnabled() { + vpnStatus = "enabled" + } else { + vpnStatus = "disabled" + } + r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) + } else { + r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + } } conntrack.Close() @@ -988,13 +994,10 @@ func (r *Router) notifyNetworkUpdate(int) error { for _, outbound := range r.outbounds { listener, isListener := outbound.(adapter.InterfaceUpdateListener) if isListener { - err := listener.InterfaceUpdated() - if err != nil { - return err - } + listener.InterfaceUpdated() } } - return nil + return } func (r *Router) ResetNetwork() error { @@ -1003,10 +1006,7 @@ func (r *Router) ResetNetwork() error { for _, outbound := range r.outbounds { listener, isListener := outbound.(adapter.InterfaceUpdateListener) if isListener { - err := listener.InterfaceUpdated() - if err != nil { - return err - } + listener.InterfaceUpdated() } } return nil diff --git a/test/go.mod b/test/go.mod index ad76cd38..c0c41a4c 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v20.10.18+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid/v5 v5.0.0 - github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee + github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/spyzhov/ajson v0.7.1 @@ -76,7 +76,7 @@ require ( github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 // indirect github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect - github.com/sagernet/sing-tun v0.1.11 // indirect + github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 // indirect github.com/sagernet/sing-vmess v0.1.7 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect diff --git a/test/go.sum b/test/go.sum index 3ed47839..6f5c6727 100644 --- a/test/go.sum +++ b/test/go.sum @@ -132,8 +132,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee h1:5MATgtWMh2TCAVMtQnC3UcVMympANU7zXEekctD29PY= -github.com/sagernet/sing v0.2.10-0.20230802105922-c6a69b4912ee/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a h1:b89t6Mjgk4rJ5lrNMnCzy1/J116XkhgdB3YNd9FHyF4= +github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 h1:IQ7oBBKz+lwIqwI9IMStlQ9YSUu3eKJmNTip0aLbvOI= @@ -144,8 +144,8 @@ github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbs github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.1.11 h1:wUfRQZ4eHk8suHkGKEFxjV5uXl3tfZhPm/v14/4lHvk= -github.com/sagernet/sing-tun v0.1.11/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= +github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 h1:f1ejTKI6R+rQ2vHyD5pNHy0V+MhfBD1l8wiYyhTscnI= +github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4= github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index ae93d52c..e3f1aed8 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -162,8 +162,11 @@ func (t *Transport) updateServers() error { } } -func (t *Transport) interfaceUpdated(int) error { - return t.updateServers() +func (t *Transport) interfaceUpdated(int) { + err := t.updateServers() + if err != nil { + t.logger.Error("update servers: ", err) + } } func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { From 3e6d34adead9b71ded2137b42d5919c345b7692a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 4 Aug 2023 21:17:16 +0800 Subject: [PATCH 14/20] Update dependencies --- docs/configuration/inbound/tun.md | 11 ++++++----- go.mod | 2 +- go.sum | 4 ++-- test/go.mod | 2 +- test/go.sum | 3 +++ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 095c3e9a..8edef4c2 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -142,11 +142,12 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes). TCP/IP stack. -| Stack | Description | Status | -|------------------|----------------------------------------------------------------------------------|-------------------| -| system (default) | Sometimes better performance | recommended | -| gVisor | Better compatibility, based on [google/gvisor](https://github.com/google/gvisor) | recommended | -| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived | +| Stack | Description | Status | +|--------|----------------------------------------------------------------------------------|-------------------| +| system | Sometimes better performance | recommended | +| gVisor | Better compatibility, based on [google/gvisor](https://github.com/google/gvisor) | recommended | +| mixed | Mixed `system` TCP stack and `gVisor` UDP stack | recommended | +| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived | !!! warning "" diff --git a/go.mod b/go.mod index 20a88a58..8a5b52ed 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.4 github.com/sagernet/sing-shadowsocks2 v0.1.3 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 + github.com/sagernet/sing-tun v0.1.12-0.20230808120247-47ab78d303db github.com/sagernet/sing-vmess v0.1.7 github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 diff --git a/go.sum b/go.sum index e593af1e..8ef5f0c1 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/sagernet/sing-shadowsocks2 v0.1.3 h1:WXoLvCFi5JTFBRYorf1YePGYIQyJ/zbs github.com/sagernet/sing-shadowsocks2 v0.1.3/go.mod h1:DOhJc/cLeqRv0wuePrQso+iUmDxOnWF4eT/oMcRzYFw= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 h1:f1ejTKI6R+rQ2vHyD5pNHy0V+MhfBD1l8wiYyhTscnI= -github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= +github.com/sagernet/sing-tun v0.1.12-0.20230808120247-47ab78d303db h1:jOwG+7u4NtQVwXj5pFnGeNnDoa2cv83O5x4NLKN8y/c= +github.com/sagernet/sing-tun v0.1.12-0.20230808120247-47ab78d303db/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4= github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= diff --git a/test/go.mod b/test/go.mod index c0c41a4c..0c6570cb 100644 --- a/test/go.mod +++ b/test/go.mod @@ -76,7 +76,7 @@ require ( github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 // indirect github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect - github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 // indirect + github.com/sagernet/sing-tun v0.1.12-0.20230808120247-47ab78d303db // indirect github.com/sagernet/sing-vmess v0.1.7 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect diff --git a/test/go.sum b/test/go.sum index 6f5c6727..a63273cb 100644 --- a/test/go.sum +++ b/test/go.sum @@ -146,6 +146,9 @@ github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnV github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873 h1:f1ejTKI6R+rQ2vHyD5pNHy0V+MhfBD1l8wiYyhTscnI= github.com/sagernet/sing-tun v0.1.12-0.20230807123152-0a68b9f1d873/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= +github.com/sagernet/sing-tun v0.1.12-0.20230807123232-b4cea12022c0/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= +github.com/sagernet/sing-tun v0.1.12-0.20230808064040-326c20ab22af/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= +github.com/sagernet/sing-tun v0.1.12-0.20230808120247-47ab78d303db/go.mod h1:XsyIVKd/Qp+2SdLZWGbavHtcpE7J7XU3S1zJmcoj9Ck= github.com/sagernet/sing-vmess v0.1.7 h1:TM8FFLsXmlXH9XT8/oDgc6PC5BOzrg6OzyEe01is2r4= github.com/sagernet/sing-vmess v0.1.7/go.mod h1:1qkC1L1T2sxnS/NuO6HU72S8TkltV+EXoKGR29m/Yss= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= From 6de680421248e02fe207d5688f3dd69c7bc00f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 23 Jul 2023 14:42:19 +0800 Subject: [PATCH 15/20] Update quic-go to v0.37.3 --- go.mod | 7 +++---- go.sum | 32 ++++++++++++++++++++++++-------- inbound/hysteria.go | 2 +- outbound/hysteria.go | 2 +- test/go.mod | 4 ++-- test/go.sum | 2 ++ 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 8a5b52ed..751a4f85 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 github.com/sagernet/gomobile v0.0.0-20230728014906-3de089147f59 github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 - github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 + github.com/sagernet/quic-go v0.0.0-20230808085251-fb2cdfa273e5 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 @@ -62,6 +62,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect @@ -76,9 +77,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-18 v0.2.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.2 // indirect - github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + github.com/quic-go/qtls-go1-20 v0.3.1 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect diff --git a/go.sum b/go.sum index 8ef5f0c1..27b0c81e 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,7 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4 github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -93,12 +94,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= -github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= -github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= -github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= -github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg= +github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I= @@ -110,8 +107,8 @@ github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTS github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 h1:BjZBIgwzlIbRSijdGQYZf0CaqHY1ZEIOUqVEKEICU0U= -github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111/go.mod h1:5rilP6WxqIl/4ypZbMjr+MK+STxuCEvO5yVtEyYNZ6g= +github.com/sagernet/quic-go v0.0.0-20230808085251-fb2cdfa273e5 h1:vg1q39d3bsG8mOAueGk+JywLnf+p6Is9imJu1eRrrs4= +github.com/sagernet/quic-go v0.0.0-20230808085251-fb2cdfa273e5/go.mod h1:w+nln6f/ZtyPpGbFxmgd5iYFVMmgS+gpD5hu5GAqC1I= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= @@ -158,6 +155,7 @@ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gV github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= @@ -173,25 +171,37 @@ go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 h1:zLxFnORHDFTSkJPawMU7LzsuGQJ4MUFS653jJHpORow= go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -200,6 +210,7 @@ golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= @@ -207,9 +218,14 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= diff --git a/inbound/hysteria.go b/inbound/hysteria.go index 9830276c..0e13bdcd 100644 --- a/inbound/hysteria.go +++ b/inbound/hysteria.go @@ -244,7 +244,7 @@ func (h *Hysteria) accept(ctx context.Context, conn quic.Connection) error { func (h *Hysteria) udpRecvLoop(conn quic.Connection) { for { - packet, err := conn.ReceiveMessage() + packet, err := conn.ReceiveMessage(h.ctx) if err != nil { return } diff --git a/outbound/hysteria.go b/outbound/hysteria.go index 2c13ac6c..d055c8f9 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -214,7 +214,7 @@ func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) { func (h *Hysteria) udpRecvLoop(conn quic.Connection) { for { - packet, err := conn.ReceiveMessage() + packet, err := conn.ReceiveMessage(h.ctx) if err != nil { return } diff --git a/test/go.mod b/test/go.mod index 0c6570cb..d1aa2149 100644 --- a/test/go.mod +++ b/test/go.mod @@ -66,12 +66,12 @@ require ( github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-18 v0.2.0 // indirect github.com/quic-go/qtls-go1-19 v0.3.2 // indirect - github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + github.com/quic-go/qtls-go1-20 v0.3.1 // indirect github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect - github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 // indirect + github.com/sagernet/quic-go v0.0.0-20230808085251-fb2cdfa273e5 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 // indirect github.com/sagernet/sing-mux v0.1.3-0.20230803070305-ea4a972acd21 // indirect diff --git a/test/go.sum b/test/go.sum index a63273cb..22e18e79 100644 --- a/test/go.sum +++ b/test/go.sum @@ -118,6 +118,7 @@ github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc8 github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= @@ -128,6 +129,7 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111 h1:BjZBIgwzlIbRSijdGQYZf0CaqHY1ZEIOUqVEKEICU0U= github.com/sagernet/quic-go v0.0.0-20230731012313-1327e4015111/go.mod h1:5rilP6WxqIl/4ypZbMjr+MK+STxuCEvO5yVtEyYNZ6g= +github.com/sagernet/quic-go v0.0.0-20230808085251-fb2cdfa273e5/go.mod h1:w+nln6f/ZtyPpGbFxmgd5iYFVMmgS+gpD5hu5GAqC1I= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= From f3f7fa9c4b63e02f10a890198914bab534bba572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 23 Jul 2023 14:42:19 +0800 Subject: [PATCH 16/20] Add TUIC protocol --- constant/proxy.go | 3 + inbound/builder.go | 2 + inbound/default.go | 11 + inbound/tuic.go | 114 ++ inbound/tuic_stub.go | 16 + option/inbound.go | 5 + option/outbound.go | 5 + option/tuic.go | 30 + outbound/builder.go | 2 + outbound/tuic.go | 123 ++ outbound/tuic_stub.go | 16 + test/clash_test.go | 4 + test/config/tuic-client.json | 14 + test/config/tuic-server.json | 9 + test/tuic_test.go | 178 +++ transport/tuic/address.go | 10 + transport/tuic/client.go | 321 ++++++ transport/tuic/client_packet.go | 110 ++ transport/tuic/congestion.go | 46 + transport/tuic/congestion/README.md | 3 + transport/tuic/congestion/bandwidth.go | 25 + .../tuic/congestion/bandwidth_sampler.go | 374 ++++++ transport/tuic/congestion/bbr_sender.go | 1000 +++++++++++++++++ transport/tuic/congestion/clock.go | 20 + transport/tuic/congestion/cubic.go | 213 ++++ transport/tuic/congestion/cubic_sender.go | 318 ++++++ .../tuic/congestion/hybrid_slow_start.go | 112 ++ transport/tuic/congestion/minmax.go | 72 ++ transport/tuic/congestion/pacer.go | 81 ++ transport/tuic/congestion/windowed_filter.go | 132 +++ transport/tuic/packet.go | 497 ++++++++ transport/tuic/protocol.go | 15 + transport/tuic/server.go | 434 +++++++ transport/tuic/server_packet.go | 73 ++ 34 files changed, 4388 insertions(+) create mode 100644 inbound/tuic.go create mode 100644 inbound/tuic_stub.go create mode 100644 option/tuic.go create mode 100644 outbound/tuic.go create mode 100644 outbound/tuic_stub.go create mode 100644 test/config/tuic-client.json create mode 100644 test/config/tuic-server.json create mode 100644 test/tuic_test.go create mode 100644 transport/tuic/address.go create mode 100644 transport/tuic/client.go create mode 100644 transport/tuic/client_packet.go create mode 100644 transport/tuic/congestion.go create mode 100644 transport/tuic/congestion/README.md create mode 100644 transport/tuic/congestion/bandwidth.go create mode 100644 transport/tuic/congestion/bandwidth_sampler.go create mode 100644 transport/tuic/congestion/bbr_sender.go create mode 100644 transport/tuic/congestion/clock.go create mode 100644 transport/tuic/congestion/cubic.go create mode 100644 transport/tuic/congestion/cubic_sender.go create mode 100644 transport/tuic/congestion/hybrid_slow_start.go create mode 100644 transport/tuic/congestion/minmax.go create mode 100644 transport/tuic/congestion/pacer.go create mode 100644 transport/tuic/congestion/windowed_filter.go create mode 100644 transport/tuic/packet.go create mode 100644 transport/tuic/protocol.go create mode 100644 transport/tuic/server.go create mode 100644 transport/tuic/server_packet.go diff --git a/constant/proxy.go b/constant/proxy.go index 7fc12d36..2b9d8945 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -21,6 +21,7 @@ const ( TypeShadowTLS = "shadowtls" TypeShadowsocksR = "shadowsocksr" TypeVLESS = "vless" + TypeTUIC = "tuic" ) const ( @@ -62,6 +63,8 @@ func ProxyDisplayName(proxyType string) string { return "ShadowsocksR" case TypeVLESS: return "VLESS" + case TypeTUIC: + return "TUIC" case TypeSelector: return "Selector" case TypeURLTest: diff --git a/inbound/builder.go b/inbound/builder.go index 5243e6b8..4cd466af 100644 --- a/inbound/builder.go +++ b/inbound/builder.go @@ -44,6 +44,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions) case C.TypeVLESS: return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions) + case C.TypeTUIC: + return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions) default: return nil, E.New("unknown inbound type: ", options.Type) } diff --git a/inbound/default.go b/inbound/default.go index 28f8cee2..9ddfc915 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -153,6 +153,17 @@ func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.Inboun return metadata } +func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { + metadata.Inbound = a.tag + metadata.InboundType = a.protocol + metadata.InboundDetour = a.listenOptions.Detour + metadata.InboundOptions = a.listenOptions.InboundOptions + if !metadata.Destination.IsValid() { + metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + } + return metadata +} + func (a *myInboundAdapter) newError(err error) { a.logger.Error(err) } diff --git a/inbound/tuic.go b/inbound/tuic.go new file mode 100644 index 00000000..f8ce24a0 --- /dev/null +++ b/inbound/tuic.go @@ -0,0 +1,114 @@ +//go:build with_quic + +package inbound + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/tuic" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + + "github.com/gofrs/uuid/v5" +) + +var _ adapter.Inbound = (*TUIC)(nil) + +type TUIC struct { + myInboundAdapter + server *tuic.Server + tlsConfig tls.ServerConfig +} + +func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (*TUIC, error) { + options.UDPFragmentDefault = true + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + rawConfig, err := tlsConfig.Config() + if err != nil { + return nil, err + } + var users []tuic.User + for index, user := range options.Users { + if user.UUID == "" { + return nil, E.New("missing uuid for user ", index) + } + userUUID, err := uuid.FromString(user.UUID) + if err != nil { + return nil, E.Cause(err, "invalid uuid for user ", index) + } + users = append(users, tuic.User{Name: user.Name, UUID: userUUID, Password: user.Password}) + } + inbound := &TUIC{ + myInboundAdapter: myInboundAdapter{ + protocol: C.TypeTUIC, + network: []string{N.NetworkUDP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, + } + server, err := tuic.NewServer(tuic.ServerOptions{ + Context: ctx, + Logger: logger, + TLSConfig: rawConfig, + Users: users, + CongestionControl: options.CongestionControl, + AuthTimeout: time.Duration(options.AuthTimeout), + ZeroRTTHandshake: options.ZeroRTTHandshake, + Heartbeat: time.Duration(options.Heartbeat), + Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil), + }) + if err != nil { + return nil, err + } + inbound.server = server + return inbound, nil +} + +func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata = h.createMetadata(conn, metadata) + metadata.User, _ = auth.UserFromContext[string](ctx) + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + metadata = h.createPacketMetadata(conn, metadata) + metadata.User, _ = auth.UserFromContext[string](ctx) + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (h *TUIC) Start() error { + packetConn, err := h.myInboundAdapter.ListenUDP() + if err != nil { + return err + } + return h.server.Start(packetConn) +} + +func (h *TUIC) Close() error { + return common.Close( + &h.myInboundAdapter, + common.PtrOrNil(h.server), + ) +} diff --git a/inbound/tuic_stub.go b/inbound/tuic_stub.go new file mode 100644 index 00000000..bfd402ab --- /dev/null +++ b/inbound/tuic_stub.go @@ -0,0 +1,16 @@ +//go:build !with_quic + +package inbound + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + +func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded +} diff --git a/option/inbound.go b/option/inbound.go index ef56be90..b09b3a65 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -23,6 +23,7 @@ type _Inbound struct { HysteriaOptions HysteriaInboundOptions `json:"-"` ShadowTLSOptions ShadowTLSInboundOptions `json:"-"` VLESSOptions VLESSInboundOptions `json:"-"` + TUICOptions TUICInboundOptions `json:"-"` } type Inbound _Inbound @@ -58,6 +59,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) { v = h.ShadowTLSOptions case C.TypeVLESS: v = h.VLESSOptions + case C.TypeTUIC: + v = h.TUICOptions default: return nil, E.New("unknown inbound type: ", h.Type) } @@ -99,6 +102,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { v = &h.ShadowTLSOptions case C.TypeVLESS: v = &h.VLESSOptions + case C.TypeTUIC: + v = &h.TUICOptions default: return E.New("unknown inbound type: ", h.Type) } diff --git a/option/outbound.go b/option/outbound.go index 5b8eb936..ab7aa0eb 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -23,6 +23,7 @@ type _Outbound struct { ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"` ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"` VLESSOptions VLESSOutboundOptions `json:"-"` + TUICOptions TUICOutboundOptions `json:"-"` SelectorOptions SelectorOutboundOptions `json:"-"` URLTestOptions URLTestOutboundOptions `json:"-"` } @@ -60,6 +61,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) { v = h.ShadowsocksROptions case C.TypeVLESS: v = h.VLESSOptions + case C.TypeTUIC: + v = h.TUICOptions case C.TypeSelector: v = h.SelectorOptions case C.TypeURLTest: @@ -105,6 +108,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error { v = &h.ShadowsocksROptions case C.TypeVLESS: v = &h.VLESSOptions + case C.TypeTUIC: + v = &h.TUICOptions case C.TypeSelector: v = &h.SelectorOptions case C.TypeURLTest: diff --git a/option/tuic.go b/option/tuic.go new file mode 100644 index 00000000..98d48be2 --- /dev/null +++ b/option/tuic.go @@ -0,0 +1,30 @@ +package option + +type TUICInboundOptions struct { + ListenOptions + Users []TUICUser `json:"users,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + AuthTimeout Duration `json:"auth_timeout,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat Duration `json:"heartbeat,omitempty"` + TLS *InboundTLSOptions `json:"tls,omitempty"` +} + +type TUICUser struct { + Name string `json:"name,omitempty"` + UUID string `json:"uuid,omitempty"` + Password string `json:"password,omitempty"` +} + +type TUICOutboundOptions struct { + DialerOptions + ServerOptions + UUID string `json:"uuid,omitempty"` + Password string `json:"password,omitempty"` + CongestionControl string `json:"congestion_control,omitempty"` + UDPRelayMode string `json:"udp_relay_mode,omitempty"` + ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` + Heartbeat Duration `json:"heartbeat,omitempty"` + Network NetworkList `json:"network,omitempty"` + TLS *OutboundTLSOptions `json:"tls,omitempty"` +} diff --git a/outbound/builder.go b/outbound/builder.go index f32c5de6..1324fdbf 100644 --- a/outbound/builder.go +++ b/outbound/builder.go @@ -51,6 +51,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, t return NewShadowsocksR(ctx, router, logger, tag, options.ShadowsocksROptions) case C.TypeVLESS: return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) + case C.TypeTUIC: + return NewTUIC(ctx, router, logger, tag, options.TUICOptions) case C.TypeSelector: return NewSelector(router, logger, tag, options.SelectorOptions) case C.TypeURLTest: diff --git a/outbound/tuic.go b/outbound/tuic.go new file mode 100644 index 00000000..42585ab3 --- /dev/null +++ b/outbound/tuic.go @@ -0,0 +1,123 @@ +//go:build with_quic + +package outbound + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/tuic" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/gofrs/uuid/v5" +) + +var ( + _ adapter.Outbound = (*TUIC)(nil) + _ adapter.InterfaceUpdateListener = (*TUIC)(nil) +) + +type TUIC struct { + myOutboundAdapter + client *tuic.Client +} + +func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) { + options.UDPFragmentDefault = true + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + abstractTLSConfig, err := tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + tlsConfig, err := abstractTLSConfig.Config() + if err != nil { + return nil, err + } + userUUID, err := uuid.FromString(options.UUID) + if err != nil { + return nil, E.Cause(err, "invalid uuid") + } + var udpStream bool + switch options.UDPRelayMode { + case "native": + case "quic": + udpStream = true + } + client, err := tuic.NewClient(tuic.ClientOptions{ + Context: ctx, + Dialer: dialer.New(router, options.DialerOptions), + ServerAddress: options.ServerOptions.Build(), + TLSConfig: tlsConfig, + UUID: userUUID, + Password: options.Password, + CongestionControl: options.CongestionControl, + UDPStream: udpStream, + ZeroRTTHandshake: options.ZeroRTTHandshake, + Heartbeat: time.Duration(options.Heartbeat), + }) + if err != nil { + return nil, err + } + return &TUIC{ + myOutboundAdapter: myOutboundAdapter{ + protocol: C.TypeTUIC, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), + }, + client: client, + }, nil +} + +func (h *TUIC) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + switch N.NetworkName(network) { + case N.NetworkTCP: + h.logger.InfoContext(ctx, "outbound connection to ", destination) + return h.client.DialConn(ctx, destination) + case N.NetworkUDP: + conn, err := h.ListenPacket(ctx, destination) + if err != nil { + return nil, err + } + return bufio.NewBindPacketConn(conn, destination), nil + default: + return nil, E.New("unsupported network: ", network) + } +} + +func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "outbound packet connection to ", destination) + return h.client.ListenPacket(ctx) +} + +func (h *TUIC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return NewConnection(ctx, h, conn, metadata) +} + +func (h *TUIC) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewPacketConnection(ctx, h, conn, metadata) +} + +func (h *TUIC) InterfaceUpdated() { + _ = h.client.CloseWithError(E.New("network changed")) +} + +func (h *TUIC) Close() error { + return h.client.CloseWithError(os.ErrClosed) +} diff --git a/outbound/tuic_stub.go b/outbound/tuic_stub.go new file mode 100644 index 00000000..a6372c9e --- /dev/null +++ b/outbound/tuic_stub.go @@ -0,0 +1,16 @@ +//go:build !with_quic + +package outbound + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + +func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded +} diff --git a/test/clash_test.go b/test/clash_test.go index 466ba5e8..7ea03ebc 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -38,6 +38,8 @@ const ( ImageShadowsocksR = "teddysun/shadowsocks-r:latest" ImageXRayCore = "teddysun/xray:latest" ImageShadowsocksLegacy = "mritd/shadowsocks:latest" + ImageTUICServer = "" + ImageTUICClient = "" ) var allImages = []string{ @@ -53,6 +55,8 @@ var allImages = []string{ ImageShadowsocksR, ImageXRayCore, ImageShadowsocksLegacy, + // ImageTUICServer, + // ImageTUICClient, } var localIP = netip.MustParseAddr("127.0.0.1") diff --git a/test/config/tuic-client.json b/test/config/tuic-client.json new file mode 100644 index 00000000..c1042b53 --- /dev/null +++ b/test/config/tuic-client.json @@ -0,0 +1,14 @@ +{ + "relay": { + "server": "127.0.0.1:10000", + "uuid": "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D", + "password": "tuic", + "certificates": [ + "/etc/tuic/ca.pem" + ] + }, + "local": { + "server": "127.0.0.1:10001" + }, + "log_level": "debug" +} \ No newline at end of file diff --git a/test/config/tuic-server.json b/test/config/tuic-server.json new file mode 100644 index 00000000..74e83eba --- /dev/null +++ b/test/config/tuic-server.json @@ -0,0 +1,9 @@ +{ + "server": "[::]:10000", + "users": { + "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D": "tuic" + }, + "certificate": "/etc/tuic/cert.pem", + "private_key": "/etc/tuic/key.pem", + "log_level": "debug" +} \ No newline at end of file diff --git a/test/tuic_test.go b/test/tuic_test.go new file mode 100644 index 00000000..3851d132 --- /dev/null +++ b/test/tuic_test.go @@ -0,0 +1,178 @@ +package main + +import ( + "net/netip" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + + "github.com/gofrs/uuid/v5" +) + +func TestTUICSelf(t *testing.T) { + t.Run("self", func(t *testing.T) { + testTUICSelf(t, false, false) + }) + t.Run("self-udp-stream", func(t *testing.T) { + testTUICSelf(t, true, false) + }) + t.Run("self-early", func(t *testing.T) { + testTUICSelf(t, false, true) + }) +} + +func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + var udpRelayMode string + if udpStream { + udpRelayMode = "quic" + } + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeTUIC, + TUICOptions: option.TUICInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.TUICUser{{ + UUID: uuid.Nil.String(), + }}, + ZeroRTTHandshake: zeroRTTHandshake, + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTUIC, + Tag: "tuic-out", + TUICOptions: option.TUICOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: uuid.Nil.String(), + UDPRelayMode: udpRelayMode, + ZeroRTTHandshake: zeroRTTHandshake, + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "tuic-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} + +func TestTUICInbound(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeTUIC, + TUICOptions: option.TUICInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.TUICUser{{ + UUID: "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D", + Password: "tuic", + }}, + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }) + startDockerContainer(t, DockerOptions{ + Image: ImageTUICClient, + Ports: []uint16{serverPort, clientPort}, + Bind: map[string]string{ + "tuic-client.json": "/etc/tuic/config.json", + caPem: "/etc/tuic/ca.pem", + }, + }) +} + +func TestTUICOutbound(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + startDockerContainer(t, DockerOptions{ + Image: ImageTUICServer, + Ports: []uint16{testPort}, + Bind: map[string]string{ + "tuic-server.json": "/etc/tuic/config.json", + certPem: "/etc/tuic/cert.pem", + keyPem: "/etc/tuic/key.pem", + }, + }) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeTUIC, + TUICOptions: option.TUICOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D", + Password: "tuic", + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} diff --git a/transport/tuic/address.go b/transport/tuic/address.go new file mode 100644 index 00000000..22b18fa9 --- /dev/null +++ b/transport/tuic/address.go @@ -0,0 +1,10 @@ +package tuic + +import M "github.com/sagernet/sing/common/metadata" + +var addressSerializer = M.NewSerializer( + M.AddressFamilyByte(0x00, M.AddressFamilyFqdn), + M.AddressFamilyByte(0x01, M.AddressFamilyIPv4), + M.AddressFamilyByte(0x02, M.AddressFamilyIPv6), + M.AddressFamilyByte(0xff, M.AddressFamilyEmpty), +) diff --git a/transport/tuic/client.go b/transport/tuic/client.go new file mode 100644 index 00000000..c11cdf1a --- /dev/null +++ b/transport/tuic/client.go @@ -0,0 +1,321 @@ +package tuic + +import ( + "context" + "crypto/tls" + "io" + "net" + "os" + "runtime" + "sync" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing-box/common/baderror" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/gofrs/uuid/v5" +) + +type ClientOptions struct { + Context context.Context + Dialer N.Dialer + ServerAddress M.Socksaddr + TLSConfig *tls.Config + UUID uuid.UUID + Password string + CongestionControl string + UDPStream bool + ZeroRTTHandshake bool + Heartbeat time.Duration +} + +type Client struct { + ctx context.Context + dialer N.Dialer + serverAddr M.Socksaddr + tlsConfig *tls.Config + quicConfig *quic.Config + uuid uuid.UUID + password string + congestionControl string + udpStream bool + zeroRTTHandshake bool + heartbeat time.Duration + + connAccess sync.RWMutex + conn *clientQUICConnection +} + +func NewClient(options ClientOptions) (*Client, error) { + if options.Heartbeat == 0 { + options.Heartbeat = 10 * time.Second + } + quicConfig := &quic.Config{ + DisablePathMTUDiscovery: !(runtime.GOOS == "windows" || runtime.GOOS == "linux" || runtime.GOOS == "android" || runtime.GOOS == "darwin"), + MaxDatagramFrameSize: 1400, + EnableDatagrams: true, + } + switch options.CongestionControl { + case "": + options.CongestionControl = "cubic" + case "cubic", "new_reno", "bbr": + default: + return nil, E.New("unknown congestion control algorithm: ", options.CongestionControl) + } + return &Client{ + ctx: options.Context, + dialer: options.Dialer, + serverAddr: options.ServerAddress, + tlsConfig: options.TLSConfig, + quicConfig: quicConfig, + uuid: options.UUID, + password: options.Password, + congestionControl: options.CongestionControl, + udpStream: options.UDPStream, + zeroRTTHandshake: options.ZeroRTTHandshake, + heartbeat: options.Heartbeat, + }, nil +} + +func (c *Client) offer(ctx context.Context) (*clientQUICConnection, error) { + conn := c.conn + if conn != nil && conn.active() { + return conn, nil + } + c.connAccess.Lock() + defer c.connAccess.Unlock() + conn = c.conn + if conn != nil && conn.active() { + return conn, nil + } + conn, err := c.offerNew(ctx) + if err != nil { + return nil, err + } + return conn, nil +} + +func (c *Client) offerNew(ctx context.Context) (*clientQUICConnection, error) { + udpConn, err := c.dialer.DialContext(ctx, "udp", c.serverAddr) + if err != nil { + return nil, err + } + var quicConn quic.Connection + if c.zeroRTTHandshake { + quicConn, err = quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig) + } else { + quicConn, err = quic.Dial(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig) + } + if err != nil { + udpConn.Close() + return nil, E.Cause(err, "open connection") + } + setCongestion(c.ctx, quicConn, c.congestionControl) + conn := &clientQUICConnection{ + quicConn: quicConn, + rawConn: udpConn, + connDone: make(chan struct{}), + udpConnMap: make(map[uint16]*udpPacketConn), + } + go func() { + hErr := c.clientHandshake(quicConn) + if hErr != nil { + conn.closeWithError(hErr) + } + }() + if c.udpStream { + go c.loopUniStreams(conn) + } + go c.loopMessages(conn) + go c.loopHeartbeats(conn) + c.conn = conn + return conn, nil +} + +func (c *Client) clientHandshake(conn quic.Connection) error { + authStream, err := conn.OpenUniStream() + if err != nil { + return err + } + defer authStream.Close() + handshakeState := conn.ConnectionState().TLS + tuicAuthToken, err := handshakeState.ExportKeyingMaterial(string(c.uuid[:]), []byte(c.password), 32) + if err != nil { + return err + } + authRequest := buf.NewSize(AuthenticateLen) + authRequest.WriteByte(Version) + authRequest.WriteByte(CommandAuthenticate) + authRequest.Write(c.uuid[:]) + authRequest.Write(tuicAuthToken) + return common.Error(authStream.Write(authRequest.Bytes())) +} + +func (c *Client) loopHeartbeats(conn *clientQUICConnection) { + ticker := time.NewTicker(c.heartbeat) + defer ticker.Stop() + for { + select { + case <-conn.connDone: + return + case <-ticker.C: + err := conn.quicConn.SendMessage([]byte{Version, CommandHeartbeat}) + if err != nil { + conn.closeWithError(E.Cause(err, "send heartbeat")) + } + } + } +} + +func (c *Client) DialConn(ctx context.Context, destination M.Socksaddr) (net.Conn, error) { + conn, err := c.offer(ctx) + if err != nil { + return nil, err + } + stream, err := conn.quicConn.OpenStream() + if err != nil { + return nil, err + } + return &clientConn{ + parent: conn, + stream: stream, + destination: destination, + }, nil +} + +func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) { + conn, err := c.offer(ctx) + if err != nil { + return nil, err + } + var sessionID uint16 + clientPacketConn := newUDPPacketConn(ctx, conn.quicConn, c.udpStream, false, func() { + conn.udpAccess.Lock() + delete(conn.udpConnMap, sessionID) + conn.udpAccess.Unlock() + }) + conn.udpAccess.Lock() + sessionID = conn.udpSessionID + conn.udpSessionID++ + conn.udpConnMap[sessionID] = clientPacketConn + conn.udpAccess.Unlock() + clientPacketConn.sessionID = sessionID + return clientPacketConn, nil +} + +func (c *Client) CloseWithError(err error) error { + conn := c.conn + if conn != nil { + conn.closeWithError(err) + } + return nil +} + +type clientQUICConnection struct { + quicConn quic.Connection + rawConn io.Closer + closeOnce sync.Once + connDone chan struct{} + connErr error + udpAccess sync.RWMutex + udpConnMap map[uint16]*udpPacketConn + udpSessionID uint16 +} + +func (c *clientQUICConnection) active() bool { + select { + case <-c.quicConn.Context().Done(): + return false + default: + } + select { + case <-c.connDone: + return false + default: + } + return true +} + +func (c *clientQUICConnection) closeWithError(err error) { + c.closeOnce.Do(func() { + c.connErr = err + close(c.connDone) + _ = c.quicConn.CloseWithError(0, "") + _ = c.rawConn.Close() + }) +} + +type clientConn struct { + parent *clientQUICConnection + stream quic.Stream + destination M.Socksaddr + requestWritten bool +} + +func (c *clientConn) Read(b []byte) (n int, err error) { + n, err = c.stream.Read(b) + return n, baderror.WrapQUIC(err) +} + +func (c *clientConn) Write(b []byte) (n int, err error) { + if !c.requestWritten { + request := buf.NewSize(2 + addressSerializer.AddrPortLen(c.destination) + len(b)) + request.WriteByte(Version) + request.WriteByte(CommandConnect) + addressSerializer.WriteAddrPort(request, c.destination) + request.Write(b) + _, err = c.stream.Write(request.Bytes()) + if err != nil { + c.parent.closeWithError(E.Cause(err, "create new connection")) + return 0, baderror.WrapQUIC(err) + } + c.requestWritten = true + return len(b), nil + } + n, err = c.stream.Write(b) + return n, baderror.WrapQUIC(err) +} + +func (c *clientConn) Close() error { + stream := c.stream + if stream == nil { + return nil + } + stream.CancelRead(0) + return stream.Close() +} + +func (c *clientConn) LocalAddr() net.Addr { + return M.Socksaddr{} +} + +func (c *clientConn) RemoteAddr() net.Addr { + return c.destination +} + +func (c *clientConn) SetDeadline(t time.Time) error { + if c.stream == nil { + return os.ErrInvalid + } + return c.stream.SetDeadline(t) +} + +func (c *clientConn) SetReadDeadline(t time.Time) error { + if c.stream == nil { + return os.ErrInvalid + } + return c.stream.SetReadDeadline(t) +} + +func (c *clientConn) SetWriteDeadline(t time.Time) error { + if c.stream == nil { + return os.ErrInvalid + } + return c.stream.SetWriteDeadline(t) +} diff --git a/transport/tuic/client_packet.go b/transport/tuic/client_packet.go new file mode 100644 index 00000000..b4292e94 --- /dev/null +++ b/transport/tuic/client_packet.go @@ -0,0 +1,110 @@ +package tuic + +import ( + "io" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" +) + +func (c *Client) loopMessages(conn *clientQUICConnection) { + for { + message, err := conn.quicConn.ReceiveMessage(c.ctx) + if err != nil { + conn.closeWithError(E.Cause(err, "receive message")) + return + } + go func() { + hErr := c.handleMessage(conn, message) + if hErr != nil { + conn.closeWithError(E.Cause(hErr, "handle message")) + } + }() + } +} + +func (c *Client) handleMessage(conn *clientQUICConnection, data []byte) error { + if len(data) < 2 { + return E.New("invalid message") + } + if data[0] != Version { + return E.New("unknown version ", data[0]) + } + switch data[1] { + case CommandPacket: + message := udpMessagePool.Get().(*udpMessage) + err := decodeUDPMessage(message, data[2:]) + if err != nil { + message.release() + return E.Cause(err, "decode UDP message") + } + conn.handleUDPMessage(message) + return nil + case CommandHeartbeat: + return nil + default: + return E.New("unknown command ", data[0]) + } +} + +func (c *Client) loopUniStreams(conn *clientQUICConnection) { + for { + stream, err := conn.quicConn.AcceptUniStream(c.ctx) + if err != nil { + conn.closeWithError(E.Cause(err, "handle uni stream")) + return + } + go func() { + hErr := c.handleUniStream(conn, stream) + if hErr != nil { + conn.closeWithError(hErr) + } + }() + } +} + +func (c *Client) handleUniStream(conn *clientQUICConnection, stream quic.ReceiveStream) error { + defer stream.CancelRead(0) + buffer := buf.NewPacket() + defer buffer.Release() + _, err := buffer.ReadAtLeastFrom(stream, 2) + if err != nil { + return err + } + version, _ := buffer.ReadByte() + if version != Version { + return E.New("unknown version ", version) + } + command, _ := buffer.ReadByte() + if command != CommandPacket { + return E.New("unknown command ", command) + } + reader := io.MultiReader(bufio.NewCachedReader(stream, buffer), stream) + message := udpMessagePool.Get().(*udpMessage) + err = readUDPMessage(message, reader) + if err != nil { + message.release() + return err + } + conn.handleUDPMessage(message) + return nil +} + +func (c *clientQUICConnection) handleUDPMessage(message *udpMessage) { + c.udpAccess.RLock() + udpConn, loaded := c.udpConnMap[message.sessionID] + c.udpAccess.RUnlock() + if !loaded { + message.releaseMessage() + return + } + select { + case <-udpConn.ctx.Done(): + message.releaseMessage() + return + default: + } + udpConn.inputPacket(message) +} diff --git a/transport/tuic/congestion.go b/transport/tuic/congestion.go new file mode 100644 index 00000000..71f74838 --- /dev/null +++ b/transport/tuic/congestion.go @@ -0,0 +1,46 @@ +package tuic + +import ( + "context" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing-box/transport/tuic/congestion" + "github.com/sagernet/sing/common/ntp" +) + +func setCongestion(ctx context.Context, connection quic.Connection, congestionName string) { + timeFunc := ntp.TimeFuncFromContext(ctx) + if timeFunc == nil { + timeFunc = time.Now + } + switch congestionName { + case "cubic": + connection.SetCongestionControl( + congestion.NewCubicSender( + congestion.DefaultClock{TimeFunc: timeFunc}, + congestion.GetInitialPacketSize(connection.RemoteAddr()), + false, + nil, + ), + ) + case "new_reno": + connection.SetCongestionControl( + congestion.NewCubicSender( + congestion.DefaultClock{TimeFunc: timeFunc}, + congestion.GetInitialPacketSize(connection.RemoteAddr()), + true, + nil, + ), + ) + case "bbr": + connection.SetCongestionControl( + congestion.NewBBRSender( + congestion.DefaultClock{}, + congestion.GetInitialPacketSize(connection.RemoteAddr()), + congestion.InitialCongestionWindow*congestion.InitialMaxDatagramSize, + congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize, + ), + ) + } +} diff --git a/transport/tuic/congestion/README.md b/transport/tuic/congestion/README.md new file mode 100644 index 00000000..6aa0309d --- /dev/null +++ b/transport/tuic/congestion/README.md @@ -0,0 +1,3 @@ +# congestion + +mod from https://github.com/MetaCubeX/Clash.Meta/tree/53f9e1ee7104473da2b4ff5da29965563084482d/transport/tuic/congestion \ No newline at end of file diff --git a/transport/tuic/congestion/bandwidth.go b/transport/tuic/congestion/bandwidth.go new file mode 100644 index 00000000..23393bad --- /dev/null +++ b/transport/tuic/congestion/bandwidth.go @@ -0,0 +1,25 @@ +package congestion + +import ( + "math" + "time" + + "github.com/sagernet/quic-go/congestion" +) + +// Bandwidth of a connection +type Bandwidth uint64 + +const infBandwidth Bandwidth = math.MaxUint64 + +const ( + // BitsPerSecond is 1 bit per second + BitsPerSecond Bandwidth = 1 + // BytesPerSecond is 1 byte per second + BytesPerSecond = 8 * BitsPerSecond +) + +// BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta +func BandwidthFromDelta(bytes congestion.ByteCount, delta time.Duration) Bandwidth { + return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond +} diff --git a/transport/tuic/congestion/bandwidth_sampler.go b/transport/tuic/congestion/bandwidth_sampler.go new file mode 100644 index 00000000..908f6e0d --- /dev/null +++ b/transport/tuic/congestion/bandwidth_sampler.go @@ -0,0 +1,374 @@ +package congestion + +import ( + "math" + "time" + + "github.com/sagernet/quic-go/congestion" +) + +var InfiniteBandwidth = Bandwidth(math.MaxUint64) + +// SendTimeState is a subset of ConnectionStateOnSentPacket which is returned +// to the caller when the packet is acked or lost. +type SendTimeState struct { + // Whether other states in this object is valid. + isValid bool + // Whether the sender is app limited at the time the packet was sent. + // App limited bandwidth sample might be artificially low because the sender + // did not have enough data to send in order to saturate the link. + isAppLimited bool + // Total number of sent bytes at the time the packet was sent. + // Includes the packet itself. + totalBytesSent congestion.ByteCount + // Total number of acked bytes at the time the packet was sent. + totalBytesAcked congestion.ByteCount + // Total number of lost bytes at the time the packet was sent. + totalBytesLost congestion.ByteCount +} + +// ConnectionStateOnSentPacket represents the information about a sent packet +// and the state of the connection at the moment the packet was sent, +// specifically the information about the most recently acknowledged packet at +// that moment. +type ConnectionStateOnSentPacket struct { + packetNumber congestion.PacketNumber + // Time at which the packet is sent. + sendTime time.Time + // Size of the packet. + size congestion.ByteCount + // The value of |totalBytesSentAtLastAckedPacket| at the time the + // packet was sent. + totalBytesSentAtLastAckedPacket congestion.ByteCount + // The value of |lastAckedPacketSentTime| at the time the packet was + // sent. + lastAckedPacketSentTime time.Time + // The value of |lastAckedPacketAckTime| at the time the packet was + // sent. + lastAckedPacketAckTime time.Time + // Send time states that are returned to the congestion controller when the + // packet is acked or lost. + sendTimeState SendTimeState +} + +// BandwidthSample +type BandwidthSample struct { + // The bandwidth at that particular sample. Zero if no valid bandwidth sample + // is available. + bandwidth Bandwidth + // The RTT measurement at this particular sample. Zero if no RTT sample is + // available. Does not correct for delayed ack time. + rtt time.Duration + // States captured when the packet was sent. + stateAtSend SendTimeState +} + +func NewBandwidthSample() *BandwidthSample { + return &BandwidthSample{ + // FIXME: the default value of original code is zero. + rtt: InfiniteRTT, + } +} + +// BandwidthSampler keeps track of sent and acknowledged packets and outputs a +// bandwidth sample for every packet acknowledged. The samples are taken for +// individual packets, and are not filtered; the consumer has to filter the +// bandwidth samples itself. In certain cases, the sampler will locally severely +// underestimate the bandwidth, hence a maximum filter with a size of at least +// one RTT is recommended. +// +// This class bases its samples on the slope of two curves: the number of bytes +// sent over time, and the number of bytes acknowledged as received over time. +// It produces a sample of both slopes for every packet that gets acknowledged, +// based on a slope between two points on each of the corresponding curves. Note +// that due to the packet loss, the number of bytes on each curve might get +// further and further away from each other, meaning that it is not feasible to +// compare byte values coming from different curves with each other. +// +// The obvious points for measuring slope sample are the ones corresponding to +// the packet that was just acknowledged. Let us denote them as S_1 (point at +// which the current packet was sent) and A_1 (point at which the current packet +// was acknowledged). However, taking a slope requires two points on each line, +// so estimating bandwidth requires picking a packet in the past with respect to +// which the slope is measured. +// +// For that purpose, BandwidthSampler always keeps track of the most recently +// acknowledged packet, and records it together with every outgoing packet. +// When a packet gets acknowledged (A_1), it has not only information about when +// it itself was sent (S_1), but also the information about the latest +// acknowledged packet right before it was sent (S_0 and A_0). +// +// Based on that data, send and ack rate are estimated as: +// +// send_rate = (bytes(S_1) - bytes(S_0)) / (time(S_1) - time(S_0)) +// ack_rate = (bytes(A_1) - bytes(A_0)) / (time(A_1) - time(A_0)) +// +// Here, the ack rate is intuitively the rate we want to treat as bandwidth. +// However, in certain cases (e.g. ack compression) the ack rate at a point may +// end up higher than the rate at which the data was originally sent, which is +// not indicative of the real bandwidth. Hence, we use the send rate as an upper +// bound, and the sample value is +// +// rate_sample = min(send_rate, ack_rate) +// +// An important edge case handled by the sampler is tracking the app-limited +// samples. There are multiple meaning of "app-limited" used interchangeably, +// hence it is important to understand and to be able to distinguish between +// them. +// +// Meaning 1: connection state. The connection is said to be app-limited when +// there is no outstanding data to send. This means that certain bandwidth +// samples in the future would not be an accurate indication of the link +// capacity, and it is important to inform consumer about that. Whenever +// connection becomes app-limited, the sampler is notified via OnAppLimited() +// method. +// +// Meaning 2: a phase in the bandwidth sampler. As soon as the bandwidth +// sampler becomes notified about the connection being app-limited, it enters +// app-limited phase. In that phase, all *sent* packets are marked as +// app-limited. Note that the connection itself does not have to be +// app-limited during the app-limited phase, and in fact it will not be +// (otherwise how would it send packets?). The boolean flag below indicates +// whether the sampler is in that phase. +// +// Meaning 3: a flag on the sent packet and on the sample. If a sent packet is +// sent during the app-limited phase, the resulting sample related to the +// packet will be marked as app-limited. +// +// With the terminology issue out of the way, let us consider the question of +// what kind of situation it addresses. +// +// Consider a scenario where we first send packets 1 to 20 at a regular +// bandwidth, and then immediately run out of data. After a few seconds, we send +// packets 21 to 60, and only receive ack for 21 between sending packets 40 and +// 41. In this case, when we sample bandwidth for packets 21 to 40, the S_0/A_0 +// we use to compute the slope is going to be packet 20, a few seconds apart +// from the current packet, hence the resulting estimate would be extremely low +// and not indicative of anything. Only at packet 41 the S_0/A_0 will become 21, +// meaning that the bandwidth sample would exclude the quiescence. +// +// Based on the analysis of that scenario, we implement the following rule: once +// OnAppLimited() is called, all sent packets will produce app-limited samples +// up until an ack for a packet that was sent after OnAppLimited() was called. +// Note that while the scenario above is not the only scenario when the +// connection is app-limited, the approach works in other cases too. +type BandwidthSampler struct { + // The total number of congestion controlled bytes sent during the connection. + totalBytesSent congestion.ByteCount + // The total number of congestion controlled bytes which were acknowledged. + totalBytesAcked congestion.ByteCount + // The total number of congestion controlled bytes which were lost. + totalBytesLost congestion.ByteCount + // The value of |totalBytesSent| at the time the last acknowledged packet + // was sent. Valid only when |lastAckedPacketSentTime| is valid. + totalBytesSentAtLastAckedPacket congestion.ByteCount + // The time at which the last acknowledged packet was sent. Set to + // QuicTime::Zero() if no valid timestamp is available. + lastAckedPacketSentTime time.Time + // The time at which the most recent packet was acknowledged. + lastAckedPacketAckTime time.Time + // The most recently sent packet. + lastSendPacket congestion.PacketNumber + // Indicates whether the bandwidth sampler is currently in an app-limited + // phase. + isAppLimited bool + // The packet that will be acknowledged after this one will cause the sampler + // to exit the app-limited phase. + endOfAppLimitedPhase congestion.PacketNumber + // Record of the connection state at the point where each packet in flight was + // sent, indexed by the packet number. + connectionStats *ConnectionStates +} + +func NewBandwidthSampler() *BandwidthSampler { + return &BandwidthSampler{ + connectionStats: &ConnectionStates{ + stats: make(map[congestion.PacketNumber]*ConnectionStateOnSentPacket), + }, + } +} + +// OnPacketSent Inputs the sent packet information into the sampler. Assumes that all +// packets are sent in order. The information about the packet will not be +// released from the sampler until it the packet is either acknowledged or +// declared lost. +func (s *BandwidthSampler) OnPacketSent(sentTime time.Time, lastSentPacket congestion.PacketNumber, sentBytes, bytesInFlight congestion.ByteCount, hasRetransmittableData bool) { + s.lastSendPacket = lastSentPacket + + if !hasRetransmittableData { + return + } + + s.totalBytesSent += sentBytes + + // If there are no packets in flight, the time at which the new transmission + // opens can be treated as the A_0 point for the purpose of bandwidth + // sampling. This underestimates bandwidth to some extent, and produces some + // artificially low samples for most packets in flight, but it provides with + // samples at important points where we would not have them otherwise, most + // importantly at the beginning of the connection. + if bytesInFlight == 0 { + s.lastAckedPacketAckTime = sentTime + s.totalBytesSentAtLastAckedPacket = s.totalBytesSent + + // In this situation ack compression is not a concern, set send rate to + // effectively infinite. + s.lastAckedPacketSentTime = sentTime + } + + s.connectionStats.Insert(lastSentPacket, sentTime, sentBytes, s) +} + +// OnPacketAcked Notifies the sampler that the |lastAckedPacket| is acknowledged. Returns a +// bandwidth sample. If no bandwidth sample is available, +// QuicBandwidth::Zero() is returned. +func (s *BandwidthSampler) OnPacketAcked(ackTime time.Time, lastAckedPacket congestion.PacketNumber) *BandwidthSample { + sentPacketState := s.connectionStats.Get(lastAckedPacket) + if sentPacketState == nil { + return NewBandwidthSample() + } + + sample := s.onPacketAckedInner(ackTime, lastAckedPacket, sentPacketState) + s.connectionStats.Remove(lastAckedPacket) + + return sample +} + +// onPacketAckedInner Handles the actual bandwidth calculations, whereas the outer method handles +// retrieving and removing |sentPacket|. +func (s *BandwidthSampler) onPacketAckedInner(ackTime time.Time, lastAckedPacket congestion.PacketNumber, sentPacket *ConnectionStateOnSentPacket) *BandwidthSample { + s.totalBytesAcked += sentPacket.size + + s.totalBytesSentAtLastAckedPacket = sentPacket.sendTimeState.totalBytesSent + s.lastAckedPacketSentTime = sentPacket.sendTime + s.lastAckedPacketAckTime = ackTime + + // Exit app-limited phase once a packet that was sent while the connection is + // not app-limited is acknowledged. + if s.isAppLimited && lastAckedPacket > s.endOfAppLimitedPhase { + s.isAppLimited = false + } + + // There might have been no packets acknowledged at the moment when the + // current packet was sent. In that case, there is no bandwidth sample to + // make. + if sentPacket.lastAckedPacketSentTime.IsZero() { + return NewBandwidthSample() + } + + // Infinite rate indicates that the sampler is supposed to discard the + // current send rate sample and use only the ack rate. + sendRate := InfiniteBandwidth + if sentPacket.sendTime.After(sentPacket.lastAckedPacketSentTime) { + sendRate = BandwidthFromDelta(sentPacket.sendTimeState.totalBytesSent-sentPacket.totalBytesSentAtLastAckedPacket, sentPacket.sendTime.Sub(sentPacket.lastAckedPacketSentTime)) + } + + // During the slope calculation, ensure that ack time of the current packet is + // always larger than the time of the previous packet, otherwise division by + // zero or integer underflow can occur. + if !ackTime.After(sentPacket.lastAckedPacketAckTime) { + // TODO(wub): Compare this code count before and after fixing clock jitter + // issue. + // if sentPacket.lastAckedPacketAckTime.Equal(sentPacket.sendTime) { + // This is the 1st packet after quiescense. + // QUIC_CODE_COUNT_N(quic_prev_ack_time_larger_than_current_ack_time, 1, 2); + // } else { + // QUIC_CODE_COUNT_N(quic_prev_ack_time_larger_than_current_ack_time, 2, 2); + // } + + return NewBandwidthSample() + } + + ackRate := BandwidthFromDelta(s.totalBytesAcked-sentPacket.sendTimeState.totalBytesAcked, + ackTime.Sub(sentPacket.lastAckedPacketAckTime)) + + // Note: this sample does not account for delayed acknowledgement time. This + // means that the RTT measurements here can be artificially high, especially + // on low bandwidth connections. + sample := &BandwidthSample{ + bandwidth: minBandwidth(sendRate, ackRate), + rtt: ackTime.Sub(sentPacket.sendTime), + } + + SentPacketToSendTimeState(sentPacket, &sample.stateAtSend) + return sample +} + +// OnPacketLost Informs the sampler that a packet is considered lost and it should no +// longer keep track of it. +func (s *BandwidthSampler) OnPacketLost(packetNumber congestion.PacketNumber) SendTimeState { + ok, sentPacket := s.connectionStats.Remove(packetNumber) + sendTimeState := SendTimeState{ + isValid: ok, + } + if sentPacket != nil { + s.totalBytesLost += sentPacket.size + SentPacketToSendTimeState(sentPacket, &sendTimeState) + } + + return sendTimeState +} + +// OnAppLimited Informs the sampler that the connection is currently app-limited, causing +// the sampler to enter the app-limited phase. The phase will expire by +// itself. +func (s *BandwidthSampler) OnAppLimited() { + s.isAppLimited = true + s.endOfAppLimitedPhase = s.lastSendPacket +} + +// SentPacketToSendTimeState Copy a subset of the (private) ConnectionStateOnSentPacket to the (public) +// SendTimeState. Always set send_time_state->is_valid to true. +func SentPacketToSendTimeState(sentPacket *ConnectionStateOnSentPacket, sendTimeState *SendTimeState) { + sendTimeState.isAppLimited = sentPacket.sendTimeState.isAppLimited + sendTimeState.totalBytesSent = sentPacket.sendTimeState.totalBytesSent + sendTimeState.totalBytesAcked = sentPacket.sendTimeState.totalBytesAcked + sendTimeState.totalBytesLost = sentPacket.sendTimeState.totalBytesLost + sendTimeState.isValid = true +} + +// ConnectionStates Record of the connection state at the point where each packet in flight was +// sent, indexed by the packet number. +// FIXME: using LinkedList replace map to fast remove all the packets lower than the specified packet number. +type ConnectionStates struct { + stats map[congestion.PacketNumber]*ConnectionStateOnSentPacket +} + +func (s *ConnectionStates) Insert(packetNumber congestion.PacketNumber, sentTime time.Time, bytes congestion.ByteCount, sampler *BandwidthSampler) bool { + if _, ok := s.stats[packetNumber]; ok { + return false + } + + s.stats[packetNumber] = NewConnectionStateOnSentPacket(packetNumber, sentTime, bytes, sampler) + return true +} + +func (s *ConnectionStates) Get(packetNumber congestion.PacketNumber) *ConnectionStateOnSentPacket { + return s.stats[packetNumber] +} + +func (s *ConnectionStates) Remove(packetNumber congestion.PacketNumber) (bool, *ConnectionStateOnSentPacket) { + state, ok := s.stats[packetNumber] + if ok { + delete(s.stats, packetNumber) + } + return ok, state +} + +func NewConnectionStateOnSentPacket(packetNumber congestion.PacketNumber, sentTime time.Time, bytes congestion.ByteCount, sampler *BandwidthSampler) *ConnectionStateOnSentPacket { + return &ConnectionStateOnSentPacket{ + packetNumber: packetNumber, + sendTime: sentTime, + size: bytes, + lastAckedPacketSentTime: sampler.lastAckedPacketSentTime, + lastAckedPacketAckTime: sampler.lastAckedPacketAckTime, + totalBytesSentAtLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket, + sendTimeState: SendTimeState{ + isValid: true, + isAppLimited: sampler.isAppLimited, + totalBytesSent: sampler.totalBytesSent, + totalBytesAcked: sampler.totalBytesAcked, + totalBytesLost: sampler.totalBytesLost, + }, + } +} diff --git a/transport/tuic/congestion/bbr_sender.go b/transport/tuic/congestion/bbr_sender.go new file mode 100644 index 00000000..34acc676 --- /dev/null +++ b/transport/tuic/congestion/bbr_sender.go @@ -0,0 +1,1000 @@ +package congestion + +// src from https://quiche.googlesource.com/quiche.git/+/66dea072431f94095dfc3dd2743cb94ef365f7ef/quic/core/congestion_control/bbr_sender.cc + +import ( + "fmt" + "math" + "math/rand" + "net" + "time" + + "github.com/sagernet/quic-go/congestion" +) + +const ( + // InitialMaxDatagramSize is the default maximum packet size used in QUIC for congestion window computations in bytes. + InitialMaxDatagramSize = 1252 + InitialPacketSizeIPv4 = 1252 + InitialPacketSizeIPv6 = 1232 + InitialCongestionWindow = 32 + DefaultBBRMaxCongestionWindow = 10000 +) + +func GetInitialPacketSize(addr net.Addr) congestion.ByteCount { + maxSize := congestion.ByteCount(1200) + // If this is not a UDP address, we don't know anything about the MTU. + // Use the minimum size of an Initial packet as the max packet size. + if udpAddr, ok := addr.(*net.UDPAddr); ok { + if udpAddr.IP.To4() != nil { + maxSize = InitialPacketSizeIPv4 + } else { + maxSize = InitialPacketSizeIPv6 + } + } + return congestion.ByteCount(maxSize) +} + +var ( + + // Default initial rtt used before any samples are received. + InitialRtt = 100 * time.Millisecond + + // The gain used for the STARTUP, equal to 4*ln(2). + DefaultHighGain = 2.77 + + // The gain used in STARTUP after loss has been detected. + // 1.5 is enough to allow for 25% exogenous loss and still observe a 25% growth + // in measured bandwidth. + StartupAfterLossGain = 1.5 + + // The cycle of gains used during the PROBE_BW stage. + PacingGain = []float64{1.25, 0.75, 1, 1, 1, 1, 1, 1} + + // The length of the gain cycle. + GainCycleLength = len(PacingGain) + + // The size of the bandwidth filter window, in round-trips. + BandwidthWindowSize = GainCycleLength + 2 + + // The time after which the current min_rtt value expires. + MinRttExpiry = 10 * time.Second + + // The minimum time the connection can spend in PROBE_RTT mode. + ProbeRttTime = time.Millisecond * 200 + + // If the bandwidth does not increase by the factor of |kStartupGrowthTarget| + // within |kRoundTripsWithoutGrowthBeforeExitingStartup| rounds, the connection + // will exit the STARTUP mode. + StartupGrowthTarget = 1.25 + RoundTripsWithoutGrowthBeforeExitingStartup = int64(3) + + // Coefficient of target congestion window to use when basing PROBE_RTT on BDP. + ModerateProbeRttMultiplier = 0.75 + + // Coefficient to determine if a new RTT is sufficiently similar to min_rtt that + // we don't need to enter PROBE_RTT. + SimilarMinRttThreshold = 1.125 + + // Congestion window gain for QUIC BBR during PROBE_BW phase. + DefaultCongestionWindowGainConst = 2.0 +) + +type bbrMode int + +const ( + // Startup phase of the connection. + STARTUP = iota + // After achieving the highest possible bandwidth during the startup, lower + // the pacing rate in order to drain the queue. + DRAIN + // Cruising mode. + PROBE_BW + // Temporarily slow down sending in order to empty the buffer and measure + // the real minimum RTT. + PROBE_RTT +) + +type bbrRecoveryState int + +const ( + // Do not limit. + NOT_IN_RECOVERY = iota + + // Allow an extra outstanding byte for each byte acknowledged. + CONSERVATION + + // Allow two extra outstanding bytes for each byte acknowledged (slow + // start). + GROWTH +) + +type bbrSender struct { + mode bbrMode + clock Clock + rttStats congestion.RTTStatsProvider + bytesInFlight congestion.ByteCount + // return total bytes of unacked packets. + // GetBytesInFlight func() congestion.ByteCount + // Bandwidth sampler provides BBR with the bandwidth measurements at + // individual points. + sampler *BandwidthSampler + // The number of the round trips that have occurred during the connection. + roundTripCount int64 + // The packet number of the most recently sent packet. + lastSendPacket congestion.PacketNumber + // Acknowledgement of any packet after |current_round_trip_end_| will cause + // the round trip counter to advance. + currentRoundTripEnd congestion.PacketNumber + // The filter that tracks the maximum bandwidth over the multiple recent + // round-trips. + maxBandwidth *WindowedFilter + // Tracks the maximum number of bytes acked faster than the sending rate. + maxAckHeight *WindowedFilter + // The time this aggregation started and the number of bytes acked during it. + aggregationEpochStartTime time.Time + aggregationEpochBytes congestion.ByteCount + // Minimum RTT estimate. Automatically expires within 10 seconds (and + // triggers PROBE_RTT mode) if no new value is sampled during that period. + minRtt time.Duration + // The time at which the current value of |min_rtt_| was assigned. + minRttTimestamp time.Time + // The maximum allowed number of bytes in flight. + congestionWindow congestion.ByteCount + // The initial value of the |congestion_window_|. + initialCongestionWindow congestion.ByteCount + // The largest value the |congestion_window_| can achieve. + initialMaxCongestionWindow congestion.ByteCount + // The smallest value the |congestion_window_| can achieve. + // minCongestionWindow congestion.ByteCount + // The pacing gain applied during the STARTUP phase. + highGain float64 + // The CWND gain applied during the STARTUP phase. + highCwndGain float64 + // The pacing gain applied during the DRAIN phase. + drainGain float64 + // The current pacing rate of the connection. + pacingRate Bandwidth + // The gain currently applied to the pacing rate. + pacingGain float64 + // The gain currently applied to the congestion window. + congestionWindowGain float64 + // The gain used for the congestion window during PROBE_BW. Latched from + // quic_bbr_cwnd_gain flag. + congestionWindowGainConst float64 + // The number of RTTs to stay in STARTUP mode. Defaults to 3. + numStartupRtts int64 + // If true, exit startup if 1RTT has passed with no bandwidth increase and + // the connection is in recovery. + exitStartupOnLoss bool + // Number of round-trips in PROBE_BW mode, used for determining the current + // pacing gain cycle. + cycleCurrentOffset int + // The time at which the last pacing gain cycle was started. + lastCycleStart time.Time + // Indicates whether the connection has reached the full bandwidth mode. + isAtFullBandwidth bool + // Number of rounds during which there was no significant bandwidth increase. + roundsWithoutBandwidthGain int64 + // The bandwidth compared to which the increase is measured. + bandwidthAtLastRound Bandwidth + // Set to true upon exiting quiescence. + exitingQuiescence bool + // Time at which PROBE_RTT has to be exited. Setting it to zero indicates + // that the time is yet unknown as the number of packets in flight has not + // reached the required value. + exitProbeRttAt time.Time + // Indicates whether a round-trip has passed since PROBE_RTT became active. + probeRttRoundPassed bool + // Indicates whether the most recent bandwidth sample was marked as + // app-limited. + lastSampleIsAppLimited bool + // Indicates whether any non app-limited samples have been recorded. + hasNoAppLimitedSample bool + // Indicates app-limited calls should be ignored as long as there's + // enough data inflight to see more bandwidth when necessary. + flexibleAppLimited bool + // Current state of recovery. + recoveryState bbrRecoveryState + // Receiving acknowledgement of a packet after |end_recovery_at_| will cause + // BBR to exit the recovery mode. A value above zero indicates at least one + // loss has been detected, so it must not be set back to zero. + endRecoveryAt congestion.PacketNumber + // A window used to limit the number of bytes in flight during loss recovery. + recoveryWindow congestion.ByteCount + // If true, consider all samples in recovery app-limited. + isAppLimitedRecovery bool + // When true, pace at 1.5x and disable packet conservation in STARTUP. + slowerStartup bool + // When true, disables packet conservation in STARTUP. + rateBasedStartup bool + // When non-zero, decreases the rate in STARTUP by the total number of bytes + // lost in STARTUP divided by CWND. + startupRateReductionMultiplier int64 + // Sum of bytes lost in STARTUP. + startupBytesLost congestion.ByteCount + // When true, add the most recent ack aggregation measurement during STARTUP. + enableAckAggregationDuringStartup bool + // When true, expire the windowed ack aggregation values in STARTUP when + // bandwidth increases more than 25%. + expireAckAggregationInStartup bool + // If true, will not exit low gain mode until bytes_in_flight drops below BDP + // or it's time for high gain mode. + drainToTarget bool + // If true, use a CWND of 0.75*BDP during probe_rtt instead of 4 packets. + probeRttBasedOnBdp bool + // If true, skip probe_rtt and update the timestamp of the existing min_rtt to + // now if min_rtt over the last cycle is within 12.5% of the current min_rtt. + // Even if the min_rtt is 12.5% too low, the 25% gain cycling and 2x CWND gain + // should overcome an overly small min_rtt. + probeRttSkippedIfSimilarRtt bool + // If true, disable PROBE_RTT entirely as long as the connection was recently + // app limited. + probeRttDisabledIfAppLimited bool + appLimitedSinceLastProbeRtt bool + minRttSinceLastProbeRtt time.Duration + // Latched value of --quic_always_get_bw_sample_when_acked. + alwaysGetBwSampleWhenAcked bool + + pacer *pacer + + maxDatagramSize congestion.ByteCount +} + +func NewBBRSender( + clock Clock, + initialMaxDatagramSize, + initialCongestionWindow, + initialMaxCongestionWindow congestion.ByteCount, +) *bbrSender { + b := &bbrSender{ + mode: STARTUP, + clock: clock, + sampler: NewBandwidthSampler(), + maxBandwidth: NewWindowedFilter(int64(BandwidthWindowSize), MaxFilter), + maxAckHeight: NewWindowedFilter(int64(BandwidthWindowSize), MaxFilter), + congestionWindow: initialCongestionWindow, + initialCongestionWindow: initialCongestionWindow, + highGain: DefaultHighGain, + highCwndGain: DefaultHighGain, + drainGain: 1.0 / DefaultHighGain, + pacingGain: 1.0, + congestionWindowGain: 1.0, + congestionWindowGainConst: DefaultCongestionWindowGainConst, + numStartupRtts: RoundTripsWithoutGrowthBeforeExitingStartup, + recoveryState: NOT_IN_RECOVERY, + recoveryWindow: initialMaxCongestionWindow, + minRttSinceLastProbeRtt: InfiniteRTT, + maxDatagramSize: initialMaxDatagramSize, + } + b.pacer = newPacer(b.BandwidthEstimate) + return b +} + +func (b *bbrSender) maxCongestionWindow() congestion.ByteCount { + return b.maxDatagramSize * DefaultBBRMaxCongestionWindow +} + +func (b *bbrSender) minCongestionWindow() congestion.ByteCount { + return b.maxDatagramSize * b.initialCongestionWindow +} + +func (b *bbrSender) SetRTTStatsProvider(provider congestion.RTTStatsProvider) { + b.rttStats = provider +} + +func (b *bbrSender) GetBytesInFlight() congestion.ByteCount { + return b.bytesInFlight +} + +// TimeUntilSend returns when the next packet should be sent. +func (b *bbrSender) TimeUntilSend(bytesInFlight congestion.ByteCount) time.Time { + b.bytesInFlight = bytesInFlight + return b.pacer.TimeUntilSend() +} + +func (b *bbrSender) HasPacingBudget(now time.Time) bool { + return b.pacer.Budget(now) >= b.maxDatagramSize +} + +func (b *bbrSender) SetMaxDatagramSize(s congestion.ByteCount) { + if s < b.maxDatagramSize { + panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", b.maxDatagramSize, s)) + } + cwndIsMinCwnd := b.congestionWindow == b.minCongestionWindow() + b.maxDatagramSize = s + if cwndIsMinCwnd { + b.congestionWindow = b.minCongestionWindow() + } + b.pacer.SetMaxDatagramSize(s) +} + +func (b *bbrSender) OnPacketSent(sentTime time.Time, bytesInFlight congestion.ByteCount, packetNumber congestion.PacketNumber, bytes congestion.ByteCount, isRetransmittable bool) { + b.pacer.SentPacket(sentTime, bytes) + b.lastSendPacket = packetNumber + + b.bytesInFlight = bytesInFlight + if bytesInFlight == 0 && b.sampler.isAppLimited { + b.exitingQuiescence = true + } + + if b.aggregationEpochStartTime.IsZero() { + b.aggregationEpochStartTime = sentTime + } + + b.sampler.OnPacketSent(sentTime, packetNumber, bytes, bytesInFlight, isRetransmittable) +} + +func (b *bbrSender) CanSend(bytesInFlight congestion.ByteCount) bool { + b.bytesInFlight = bytesInFlight + return bytesInFlight < b.GetCongestionWindow() +} + +func (b *bbrSender) GetCongestionWindow() congestion.ByteCount { + if b.mode == PROBE_RTT { + return b.ProbeRttCongestionWindow() + } + + if b.InRecovery() && !(b.rateBasedStartup && b.mode == STARTUP) { + return minByteCount(b.congestionWindow, b.recoveryWindow) + } + + return b.congestionWindow +} + +func (b *bbrSender) MaybeExitSlowStart() { +} + +func (b *bbrSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount, priorInFlight congestion.ByteCount, eventTime time.Time) { + totalBytesAckedBefore := b.sampler.totalBytesAcked + isRoundStart, minRttExpired := false, false + lastAckedPacket := number + + isRoundStart = b.UpdateRoundTripCounter(lastAckedPacket) + minRttExpired = b.UpdateBandwidthAndMinRtt(eventTime, number, ackedBytes) + b.UpdateRecoveryState(false, isRoundStart) + bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore + excessAcked := b.UpdateAckAggregationBytes(eventTime, bytesAcked) + + // Handle logic specific to STARTUP and DRAIN modes. + if isRoundStart && !b.isAtFullBandwidth { + b.CheckIfFullBandwidthReached() + } + b.MaybeExitStartupOrDrain(eventTime) + + // Handle logic specific to PROBE_RTT. + b.MaybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired) + + // After the model is updated, recalculate the pacing rate and congestion + // window. + b.CalculatePacingRate() + b.CalculateCongestionWindow(bytesAcked, excessAcked) + b.CalculateRecoveryWindow(bytesAcked, congestion.ByteCount(0)) +} + +func (b *bbrSender) OnPacketLost(number congestion.PacketNumber, lostBytes congestion.ByteCount, priorInFlight congestion.ByteCount) { + eventTime := time.Now() + totalBytesAckedBefore := b.sampler.totalBytesAcked + isRoundStart, minRttExpired := false, false + + b.DiscardLostPackets(number, lostBytes) + + // Input the new data into the BBR model of the connection. + var excessAcked congestion.ByteCount + + // Handle logic specific to PROBE_BW mode. + if b.mode == PROBE_BW { + b.UpdateGainCyclePhase(time.Now(), priorInFlight, true) + } + + // Handle logic specific to STARTUP and DRAIN modes. + b.MaybeExitStartupOrDrain(eventTime) + + // Handle logic specific to PROBE_RTT. + b.MaybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired) + + // Calculate number of packets acked and lost. + bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore + bytesLost := lostBytes + + // After the model is updated, recalculate the pacing rate and congestion + // window. + b.CalculatePacingRate() + b.CalculateCongestionWindow(bytesAcked, excessAcked) + b.CalculateRecoveryWindow(bytesAcked, bytesLost) +} + +//func (b *bbrSender) OnCongestionEvent(priorInFlight congestion.ByteCount, eventTime time.Time, ackedPackets, lostPackets []*congestion.Packet) { +// totalBytesAckedBefore := b.sampler.totalBytesAcked +// isRoundStart, minRttExpired := false, false +// +// if lostPackets != nil { +// b.DiscardLostPackets(lostPackets) +// } +// +// // Input the new data into the BBR model of the connection. +// var excessAcked congestion.ByteCount +// if len(ackedPackets) > 0 { +// lastAckedPacket := ackedPackets[len(ackedPackets)-1].PacketNumber +// isRoundStart = b.UpdateRoundTripCounter(lastAckedPacket) +// minRttExpired = b.UpdateBandwidthAndMinRtt(eventTime, ackedPackets) +// b.UpdateRecoveryState(lastAckedPacket, len(lostPackets) > 0, isRoundStart) +// bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore +// excessAcked = b.UpdateAckAggregationBytes(eventTime, bytesAcked) +// } +// +// // Handle logic specific to PROBE_BW mode. +// if b.mode == PROBE_BW { +// b.UpdateGainCyclePhase(eventTime, priorInFlight, len(lostPackets) > 0) +// } +// +// // Handle logic specific to STARTUP and DRAIN modes. +// if isRoundStart && !b.isAtFullBandwidth { +// b.CheckIfFullBandwidthReached() +// } +// b.MaybeExitStartupOrDrain(eventTime) +// +// // Handle logic specific to PROBE_RTT. +// b.MaybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired) +// +// // Calculate number of packets acked and lost. +// bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore +// bytesLost := congestion.ByteCount(0) +// for _, packet := range lostPackets { +// bytesLost += packet.Length +// } +// +// // After the model is updated, recalculate the pacing rate and congestion +// // window. +// b.CalculatePacingRate() +// b.CalculateCongestionWindow(bytesAcked, excessAcked) +// b.CalculateRecoveryWindow(bytesAcked, bytesLost) +//} + +//func (b *bbrSender) SetNumEmulatedConnections(n int) { +// +//} + +func (b *bbrSender) OnRetransmissionTimeout(packetsRetransmitted bool) { +} + +//func (b *bbrSender) OnConnectionMigration() { +// +//} + +//// Experiments +//func (b *bbrSender) SetSlowStartLargeReduction(enabled bool) { +// +//} + +//func (b *bbrSender) BandwidthEstimate() Bandwidth { +// return Bandwidth(b.maxBandwidth.GetBest()) +//} + +// BandwidthEstimate returns the current bandwidth estimate +func (b *bbrSender) BandwidthEstimate() Bandwidth { + if b.rttStats == nil { + return infBandwidth + } + srtt := b.rttStats.SmoothedRTT() + if srtt == 0 { + // If we haven't measured an rtt, the bandwidth estimate is unknown. + return infBandwidth + } + return BandwidthFromDelta(b.GetCongestionWindow(), srtt) +} + +//func (b *bbrSender) HybridSlowStart() *HybridSlowStart { +// return nil +//} + +//func (b *bbrSender) SlowstartThreshold() congestion.ByteCount { +// return 0 +//} + +//func (b *bbrSender) RenoBeta() float32 { +// return 0.0 +//} + +func (b *bbrSender) InRecovery() bool { + return b.recoveryState != NOT_IN_RECOVERY +} + +func (b *bbrSender) InSlowStart() bool { + return b.mode == STARTUP +} + +//func (b *bbrSender) ShouldSendProbingPacket() bool { +// if b.pacingGain <= 1 { +// return false +// } +// // TODO(b/77975811): If the pipe is highly under-utilized, consider not +// // sending a probing transmission, because the extra bandwidth is not needed. +// // If flexible_app_limited is enabled, check if the pipe is sufficiently full. +// if b.flexibleAppLimited { +// return !b.IsPipeSufficientlyFull() +// } else { +// return true +// } +//} + +//func (b *bbrSender) IsPipeSufficientlyFull() bool { +// // See if we need more bytes in flight to see more bandwidth. +// if b.mode == STARTUP { +// // STARTUP exits if it doesn't observe a 25% bandwidth increase, so the CWND +// // must be more than 25% above the target. +// return b.GetBytesInFlight() >= b.GetTargetCongestionWindow(1.5) +// } +// if b.pacingGain > 1 { +// // Super-unity PROBE_BW doesn't exit until 1.25 * BDP is achieved. +// return b.GetBytesInFlight() >= b.GetTargetCongestionWindow(b.pacingGain) +// } +// // If bytes_in_flight are above the target congestion window, it should be +// // possible to observe the same or more bandwidth if it's available. +// return b.GetBytesInFlight() >= b.GetTargetCongestionWindow(1.1) +//} + +//func (b *bbrSender) SetFromConfig() { +// // TODO: not impl. +//} + +func (b *bbrSender) UpdateRoundTripCounter(lastAckedPacket congestion.PacketNumber) bool { + if b.currentRoundTripEnd == 0 || lastAckedPacket > b.currentRoundTripEnd { + b.currentRoundTripEnd = lastAckedPacket + b.roundTripCount++ + // if b.rttStats != nil && b.InSlowStart() { + // TODO: ++stats_->slowstart_num_rtts; + // } + return true + } + return false +} + +func (b *bbrSender) UpdateBandwidthAndMinRtt(now time.Time, number congestion.PacketNumber, ackedBytes congestion.ByteCount) bool { + sampleMinRtt := InfiniteRTT + + if !b.alwaysGetBwSampleWhenAcked && ackedBytes == 0 { + // Skip acked packets with 0 in flight bytes when updating bandwidth. + return false + } + bandwidthSample := b.sampler.OnPacketAcked(now, number) + if b.alwaysGetBwSampleWhenAcked && !bandwidthSample.stateAtSend.isValid { + // From the sampler's perspective, the packet has never been sent, or the + // packet has been acked or marked as lost previously. + return false + } + b.lastSampleIsAppLimited = bandwidthSample.stateAtSend.isAppLimited + // has_non_app_limited_sample_ |= + // !bandwidth_sample.state_at_send.is_app_limited; + if !bandwidthSample.stateAtSend.isAppLimited { + b.hasNoAppLimitedSample = true + } + if bandwidthSample.rtt > 0 { + sampleMinRtt = minRtt(sampleMinRtt, bandwidthSample.rtt) + } + if !bandwidthSample.stateAtSend.isAppLimited || bandwidthSample.bandwidth > b.BandwidthEstimate() { + b.maxBandwidth.Update(int64(bandwidthSample.bandwidth), b.roundTripCount) + } + + // If none of the RTT samples are valid, return immediately. + if sampleMinRtt == InfiniteRTT { + return false + } + + b.minRttSinceLastProbeRtt = minRtt(b.minRttSinceLastProbeRtt, sampleMinRtt) + // Do not expire min_rtt if none was ever available. + minRttExpired := b.minRtt > 0 && (now.After(b.minRttTimestamp.Add(MinRttExpiry))) + if minRttExpired || sampleMinRtt < b.minRtt || b.minRtt == 0 { + if minRttExpired && b.ShouldExtendMinRttExpiry() { + minRttExpired = false + } else { + b.minRtt = sampleMinRtt + } + b.minRttTimestamp = now + // Reset since_last_probe_rtt fields. + b.minRttSinceLastProbeRtt = InfiniteRTT + b.appLimitedSinceLastProbeRtt = false + } + + return minRttExpired +} + +func (b *bbrSender) ShouldExtendMinRttExpiry() bool { + if b.probeRttDisabledIfAppLimited && b.appLimitedSinceLastProbeRtt { + // Extend the current min_rtt if we've been app limited recently. + return true + } + + minRttIncreasedSinceLastProbe := b.minRttSinceLastProbeRtt > time.Duration(float64(b.minRtt)*SimilarMinRttThreshold) + if b.probeRttSkippedIfSimilarRtt && b.appLimitedSinceLastProbeRtt && !minRttIncreasedSinceLastProbe { + // Extend the current min_rtt if we've been app limited recently and an rtt + // has been measured in that time that's less than 12.5% more than the + // current min_rtt. + return true + } + + return false +} + +func (b *bbrSender) DiscardLostPackets(number congestion.PacketNumber, lostBytes congestion.ByteCount) { + b.sampler.OnPacketLost(number) + if b.mode == STARTUP { + // if b.rttStats != nil { + // TODO: slow start. + // } + if b.startupRateReductionMultiplier != 0 { + b.startupBytesLost += lostBytes + } + } +} + +func (b *bbrSender) UpdateRecoveryState(hasLosses, isRoundStart bool) { + // Exit recovery when there are no losses for a round. + if !hasLosses { + b.endRecoveryAt = b.lastSendPacket + } + switch b.recoveryState { + case NOT_IN_RECOVERY: + // Enter conservation on the first loss. + if hasLosses { + b.recoveryState = CONSERVATION + // This will cause the |recovery_window_| to be set to the correct + // value in CalculateRecoveryWindow(). + b.recoveryWindow = 0 + // Since the conservation phase is meant to be lasting for a whole + // round, extend the current round as if it were started right now. + b.currentRoundTripEnd = b.lastSendPacket + if false && b.lastSampleIsAppLimited { + b.isAppLimitedRecovery = true + } + } + case CONSERVATION: + if isRoundStart { + b.recoveryState = GROWTH + } + fallthrough + case GROWTH: + // Exit recovery if appropriate. + if !hasLosses && b.lastSendPacket > b.endRecoveryAt { + b.recoveryState = NOT_IN_RECOVERY + b.isAppLimitedRecovery = false + } + } + + if b.recoveryState != NOT_IN_RECOVERY && b.isAppLimitedRecovery { + b.sampler.OnAppLimited() + } +} + +func (b *bbrSender) UpdateAckAggregationBytes(ackTime time.Time, ackedBytes congestion.ByteCount) congestion.ByteCount { + // Compute how many bytes are expected to be delivered, assuming max bandwidth + // is correct. + expectedAckedBytes := congestion.ByteCount(b.maxBandwidth.GetBest()) * + congestion.ByteCount((ackTime.Sub(b.aggregationEpochStartTime))) + // Reset the current aggregation epoch as soon as the ack arrival rate is less + // than or equal to the max bandwidth. + if b.aggregationEpochBytes <= expectedAckedBytes { + // Reset to start measuring a new aggregation epoch. + b.aggregationEpochBytes = ackedBytes + b.aggregationEpochStartTime = ackTime + return 0 + } + // Compute how many extra bytes were delivered vs max bandwidth. + // Include the bytes most recently acknowledged to account for stretch acks. + b.aggregationEpochBytes += ackedBytes + b.maxAckHeight.Update(int64(b.aggregationEpochBytes-expectedAckedBytes), b.roundTripCount) + return b.aggregationEpochBytes - expectedAckedBytes +} + +func (b *bbrSender) UpdateGainCyclePhase(now time.Time, priorInFlight congestion.ByteCount, hasLosses bool) { + bytesInFlight := b.GetBytesInFlight() + // In most cases, the cycle is advanced after an RTT passes. + shouldAdvanceGainCycling := now.Sub(b.lastCycleStart) > b.GetMinRtt() + + // If the pacing gain is above 1.0, the connection is trying to probe the + // bandwidth by increasing the number of bytes in flight to at least + // pacing_gain * BDP. Make sure that it actually reaches the target, as long + // as there are no losses suggesting that the buffers are not able to hold + // that much. + if b.pacingGain > 1.0 && !hasLosses && priorInFlight < b.GetTargetCongestionWindow(b.pacingGain) { + shouldAdvanceGainCycling = false + } + // If pacing gain is below 1.0, the connection is trying to drain the extra + // queue which could have been incurred by probing prior to it. If the number + // of bytes in flight falls down to the estimated BDP value earlier, conclude + // that the queue has been successfully drained and exit this cycle early. + if b.pacingGain < 1.0 && bytesInFlight <= b.GetTargetCongestionWindow(1.0) { + shouldAdvanceGainCycling = true + } + + if shouldAdvanceGainCycling { + b.cycleCurrentOffset = (b.cycleCurrentOffset + 1) % GainCycleLength + b.lastCycleStart = now + // Stay in low gain mode until the target BDP is hit. + // Low gain mode will be exited immediately when the target BDP is achieved. + if b.drainToTarget && b.pacingGain < 1.0 && PacingGain[b.cycleCurrentOffset] == 1.0 && + bytesInFlight > b.GetTargetCongestionWindow(1.0) { + return + } + b.pacingGain = PacingGain[b.cycleCurrentOffset] + } +} + +func (b *bbrSender) GetTargetCongestionWindow(gain float64) congestion.ByteCount { + bdp := congestion.ByteCount(b.GetMinRtt()) * congestion.ByteCount(b.BandwidthEstimate()) + congestionWindow := congestion.ByteCount(gain * float64(bdp)) + + // BDP estimate will be zero if no bandwidth samples are available yet. + if congestionWindow == 0 { + congestionWindow = congestion.ByteCount(gain * float64(b.initialCongestionWindow)) + } + + return maxByteCount(congestionWindow, b.minCongestionWindow()) +} + +func (b *bbrSender) CheckIfFullBandwidthReached() { + if b.lastSampleIsAppLimited { + return + } + + target := Bandwidth(float64(b.bandwidthAtLastRound) * StartupGrowthTarget) + if b.BandwidthEstimate() >= target { + b.bandwidthAtLastRound = b.BandwidthEstimate() + b.roundsWithoutBandwidthGain = 0 + if b.expireAckAggregationInStartup { + // Expire old excess delivery measurements now that bandwidth increased. + b.maxAckHeight.Reset(0, b.roundTripCount) + } + return + } + b.roundsWithoutBandwidthGain++ + if b.roundsWithoutBandwidthGain >= b.numStartupRtts || (b.exitStartupOnLoss && b.InRecovery()) { + b.isAtFullBandwidth = true + } +} + +func (b *bbrSender) MaybeExitStartupOrDrain(now time.Time) { + if b.mode == STARTUP && b.isAtFullBandwidth { + b.OnExitStartup(now) + b.mode = DRAIN + b.pacingGain = b.drainGain + b.congestionWindowGain = b.highCwndGain + } + if b.mode == DRAIN && b.GetBytesInFlight() <= b.GetTargetCongestionWindow(1) { + b.EnterProbeBandwidthMode(now) + } +} + +func (b *bbrSender) EnterProbeBandwidthMode(now time.Time) { + b.mode = PROBE_BW + b.congestionWindowGain = b.congestionWindowGainConst + + // Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is + // excluded because in that case increased gain and decreased gain would not + // follow each other. + b.cycleCurrentOffset = rand.Int() % (GainCycleLength - 1) + if b.cycleCurrentOffset >= 1 { + b.cycleCurrentOffset += 1 + } + + b.lastCycleStart = now + b.pacingGain = PacingGain[b.cycleCurrentOffset] +} + +func (b *bbrSender) MaybeEnterOrExitProbeRtt(now time.Time, isRoundStart, minRttExpired bool) { + if minRttExpired && !b.exitingQuiescence && b.mode != PROBE_RTT { + if b.InSlowStart() { + b.OnExitStartup(now) + } + b.mode = PROBE_RTT + b.pacingGain = 1.0 + // Do not decide on the time to exit PROBE_RTT until the |bytes_in_flight| + // is at the target small value. + b.exitProbeRttAt = time.Time{} + } + + if b.mode == PROBE_RTT { + b.sampler.OnAppLimited() + if b.exitProbeRttAt.IsZero() { + // If the window has reached the appropriate size, schedule exiting + // PROBE_RTT. The CWND during PROBE_RTT is kMinimumCongestionWindow, but + // we allow an extra packet since QUIC checks CWND before sending a + // packet. + if b.GetBytesInFlight() < b.ProbeRttCongestionWindow()+b.maxDatagramSize { + b.exitProbeRttAt = now.Add(ProbeRttTime) + b.probeRttRoundPassed = false + } + } else { + if isRoundStart { + b.probeRttRoundPassed = true + } + if !now.Before(b.exitProbeRttAt) && b.probeRttRoundPassed { + b.minRttTimestamp = now + if !b.isAtFullBandwidth { + b.EnterStartupMode(now) + } else { + b.EnterProbeBandwidthMode(now) + } + } + } + } + b.exitingQuiescence = false +} + +func (b *bbrSender) ProbeRttCongestionWindow() congestion.ByteCount { + if b.probeRttBasedOnBdp { + return b.GetTargetCongestionWindow(ModerateProbeRttMultiplier) + } else { + return b.minCongestionWindow() + } +} + +func (b *bbrSender) EnterStartupMode(now time.Time) { + // if b.rttStats != nil { + // TODO: slow start. + // } + b.mode = STARTUP + b.pacingGain = b.highGain + b.congestionWindowGain = b.highCwndGain +} + +func (b *bbrSender) OnExitStartup(now time.Time) { + if b.rttStats == nil { + return + } + // TODO: slow start. +} + +func (b *bbrSender) CalculatePacingRate() { + if b.BandwidthEstimate() == 0 { + return + } + + targetRate := Bandwidth(b.pacingGain * float64(b.BandwidthEstimate())) + if b.isAtFullBandwidth { + b.pacingRate = targetRate + return + } + + // Pace at the rate of initial_window / RTT as soon as RTT measurements are + // available. + if b.pacingRate == 0 && b.rttStats.MinRTT() > 0 { + b.pacingRate = BandwidthFromDelta(b.initialCongestionWindow, b.rttStats.MinRTT()) + return + } + // Slow the pacing rate in STARTUP once loss has ever been detected. + hasEverDetectedLoss := b.endRecoveryAt > 0 + if b.slowerStartup && hasEverDetectedLoss && b.hasNoAppLimitedSample { + b.pacingRate = Bandwidth(StartupAfterLossGain * float64(b.BandwidthEstimate())) + return + } + + // Slow the pacing rate in STARTUP by the bytes_lost / CWND. + if b.startupRateReductionMultiplier != 0 && hasEverDetectedLoss && b.hasNoAppLimitedSample { + b.pacingRate = Bandwidth((1.0 - (float64(b.startupBytesLost) * float64(b.startupRateReductionMultiplier) / float64(b.congestionWindow))) * float64(targetRate)) + // Ensure the pacing rate doesn't drop below the startup growth target times + // the bandwidth estimate. + b.pacingRate = maxBandwidth(b.pacingRate, Bandwidth(StartupGrowthTarget*float64(b.BandwidthEstimate()))) + return + } + + // Do not decrease the pacing rate during startup. + b.pacingRate = maxBandwidth(b.pacingRate, targetRate) +} + +func (b *bbrSender) CalculateCongestionWindow(ackedBytes, excessAcked congestion.ByteCount) { + if b.mode == PROBE_RTT { + return + } + + targetWindow := b.GetTargetCongestionWindow(b.congestionWindowGain) + if b.isAtFullBandwidth { + // Add the max recently measured ack aggregation to CWND. + targetWindow += congestion.ByteCount(b.maxAckHeight.GetBest()) + } else if b.enableAckAggregationDuringStartup { + // Add the most recent excess acked. Because CWND never decreases in + // STARTUP, this will automatically create a very localized max filter. + targetWindow += excessAcked + } + + // Instead of immediately setting the target CWND as the new one, BBR grows + // the CWND towards |target_window| by only increasing it |bytes_acked| at a + // time. + addBytesAcked := true || !b.InRecovery() + if b.isAtFullBandwidth { + b.congestionWindow = minByteCount(targetWindow, b.congestionWindow+ackedBytes) + } else if addBytesAcked && (b.congestionWindow < targetWindow || b.sampler.totalBytesAcked < b.initialCongestionWindow) { + // If the connection is not yet out of startup phase, do not decrease the + // window. + b.congestionWindow += ackedBytes + } + + // Enforce the limits on the congestion window. + b.congestionWindow = maxByteCount(b.congestionWindow, b.minCongestionWindow()) + b.congestionWindow = minByteCount(b.congestionWindow, b.maxCongestionWindow()) +} + +func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.ByteCount) { + if b.rateBasedStartup && b.mode == STARTUP { + return + } + + if b.recoveryState == NOT_IN_RECOVERY { + return + } + + // Set up the initial recovery window. + if b.recoveryWindow == 0 { + b.recoveryWindow = maxByteCount(b.GetBytesInFlight()+ackedBytes, b.minCongestionWindow()) + return + } + + // Remove losses from the recovery window, while accounting for a potential + // integer underflow. + if b.recoveryWindow >= lostBytes { + b.recoveryWindow -= lostBytes + } else { + b.recoveryWindow = congestion.ByteCount(b.maxDatagramSize) + } + // In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH, + // release additional |bytes_acked| to achieve a slow-start-like behavior. + if b.recoveryState == GROWTH { + b.recoveryWindow += ackedBytes + } + // Sanity checks. Ensure that we always allow to send at least an MSS or + // |bytes_acked| in response, whichever is larger. + b.recoveryWindow = maxByteCount(b.recoveryWindow, b.GetBytesInFlight()+ackedBytes) + b.recoveryWindow = maxByteCount(b.recoveryWindow, b.minCongestionWindow()) +} + +var _ congestion.CongestionControl = (*bbrSender)(nil) + +func (b *bbrSender) GetMinRtt() time.Duration { + if b.minRtt > 0 { + return b.minRtt + } else { + return InitialRtt + } +} + +func minRtt(a, b time.Duration) time.Duration { + if a < b { + return a + } else { + return b + } +} + +func minBandwidth(a, b Bandwidth) Bandwidth { + if a < b { + return a + } else { + return b + } +} + +func maxBandwidth(a, b Bandwidth) Bandwidth { + if a > b { + return a + } else { + return b + } +} + +func maxByteCount(a, b congestion.ByteCount) congestion.ByteCount { + if a > b { + return a + } else { + return b + } +} + +func minByteCount(a, b congestion.ByteCount) congestion.ByteCount { + if a < b { + return a + } else { + return b + } +} + +var InfiniteRTT = time.Duration(math.MaxInt64) diff --git a/transport/tuic/congestion/clock.go b/transport/tuic/congestion/clock.go new file mode 100644 index 00000000..dc3ccdc5 --- /dev/null +++ b/transport/tuic/congestion/clock.go @@ -0,0 +1,20 @@ +package congestion + +import "time" + +// A Clock returns the current time +type Clock interface { + Now() time.Time +} + +// DefaultClock implements the Clock interface using the Go stdlib clock. +type DefaultClock struct { + TimeFunc func() time.Time +} + +var _ Clock = DefaultClock{} + +// Now gets the current time +func (c DefaultClock) Now() time.Time { + return c.TimeFunc() +} diff --git a/transport/tuic/congestion/cubic.go b/transport/tuic/congestion/cubic.go new file mode 100644 index 00000000..d437c540 --- /dev/null +++ b/transport/tuic/congestion/cubic.go @@ -0,0 +1,213 @@ +package congestion + +import ( + "math" + "time" + + "github.com/sagernet/quic-go/congestion" +) + +// This cubic implementation is based on the one found in Chromiums's QUIC +// implementation, in the files net/quic/congestion_control/cubic.{hh,cc}. + +// Constants based on TCP defaults. +// The following constants are in 2^10 fractions of a second instead of ms to +// allow a 10 shift right to divide. + +// 1024*1024^3 (first 1024 is from 0.100^3) +// where 0.100 is 100 ms which is the scaling round trip time. +const ( + cubeScale = 40 + cubeCongestionWindowScale = 410 + cubeFactor congestion.ByteCount = 1 << cubeScale / cubeCongestionWindowScale / maxDatagramSize + // TODO: when re-enabling cubic, make sure to use the actual packet size here + maxDatagramSize = congestion.ByteCount(InitialPacketSizeIPv4) +) + +const defaultNumConnections = 1 + +// Default Cubic backoff factor +const beta float32 = 0.7 + +// Additional backoff factor when loss occurs in the concave part of the Cubic +// curve. This additional backoff factor is expected to give up bandwidth to +// new concurrent flows and speed up convergence. +const betaLastMax float32 = 0.85 + +// Cubic implements the cubic algorithm from TCP +type Cubic struct { + clock Clock + + // Number of connections to simulate. + numConnections int + + // Time when this cycle started, after last loss event. + epoch time.Time + + // Max congestion window used just before last loss event. + // Note: to improve fairness to other streams an additional back off is + // applied to this value if the new value is below our latest value. + lastMaxCongestionWindow congestion.ByteCount + + // Number of acked bytes since the cycle started (epoch). + ackedBytesCount congestion.ByteCount + + // TCP Reno equivalent congestion window in packets. + estimatedTCPcongestionWindow congestion.ByteCount + + // Origin point of cubic function. + originPointCongestionWindow congestion.ByteCount + + // Time to origin point of cubic function in 2^10 fractions of a second. + timeToOriginPoint uint32 + + // Last congestion window in packets computed by cubic function. + lastTargetCongestionWindow congestion.ByteCount +} + +// NewCubic returns a new Cubic instance +func NewCubic(clock Clock) *Cubic { + c := &Cubic{ + clock: clock, + numConnections: defaultNumConnections, + } + c.Reset() + return c +} + +// Reset is called after a timeout to reset the cubic state +func (c *Cubic) Reset() { + c.epoch = time.Time{} + c.lastMaxCongestionWindow = 0 + c.ackedBytesCount = 0 + c.estimatedTCPcongestionWindow = 0 + c.originPointCongestionWindow = 0 + c.timeToOriginPoint = 0 + c.lastTargetCongestionWindow = 0 +} + +func (c *Cubic) alpha() float32 { + // TCPFriendly alpha is described in Section 3.3 of the CUBIC paper. Note that + // beta here is a cwnd multiplier, and is equal to 1-beta from the paper. + // We derive the equivalent alpha for an N-connection emulation as: + b := c.beta() + return 3 * float32(c.numConnections) * float32(c.numConnections) * (1 - b) / (1 + b) +} + +func (c *Cubic) beta() float32 { + // kNConnectionBeta is the backoff factor after loss for our N-connection + // emulation, which emulates the effective backoff of an ensemble of N + // TCP-Reno connections on a single loss event. The effective multiplier is + // computed as: + return (float32(c.numConnections) - 1 + beta) / float32(c.numConnections) +} + +func (c *Cubic) betaLastMax() float32 { + // betaLastMax is the additional backoff factor after loss for our + // N-connection emulation, which emulates the additional backoff of + // an ensemble of N TCP-Reno connections on a single loss event. The + // effective multiplier is computed as: + return (float32(c.numConnections) - 1 + betaLastMax) / float32(c.numConnections) +} + +// OnApplicationLimited is called on ack arrival when sender is unable to use +// the available congestion window. Resets Cubic state during quiescence. +func (c *Cubic) OnApplicationLimited() { + // When sender is not using the available congestion window, the window does + // not grow. But to be RTT-independent, Cubic assumes that the sender has been + // using the entire window during the time since the beginning of the current + // "epoch" (the end of the last loss recovery period). Since + // application-limited periods break this assumption, we reset the epoch when + // in such a period. This reset effectively freezes congestion window growth + // through application-limited periods and allows Cubic growth to continue + // when the entire window is being used. + c.epoch = time.Time{} +} + +// CongestionWindowAfterPacketLoss computes a new congestion window to use after +// a loss event. Returns the new congestion window in packets. The new +// congestion window is a multiplicative decrease of our current window. +func (c *Cubic) CongestionWindowAfterPacketLoss(currentCongestionWindow congestion.ByteCount) congestion.ByteCount { + if currentCongestionWindow+maxDatagramSize < c.lastMaxCongestionWindow { + // We never reached the old max, so assume we are competing with another + // flow. Use our extra back off factor to allow the other flow to go up. + c.lastMaxCongestionWindow = congestion.ByteCount(c.betaLastMax() * float32(currentCongestionWindow)) + } else { + c.lastMaxCongestionWindow = currentCongestionWindow + } + c.epoch = time.Time{} // Reset time. + return congestion.ByteCount(float32(currentCongestionWindow) * c.beta()) +} + +// CongestionWindowAfterAck computes a new congestion window to use after a received ACK. +// Returns the new congestion window in packets. The new congestion window +// follows a cubic function that depends on the time passed since last +// packet loss. +func (c *Cubic) CongestionWindowAfterAck( + ackedBytes congestion.ByteCount, + currentCongestionWindow congestion.ByteCount, + delayMin time.Duration, + eventTime time.Time, +) congestion.ByteCount { + c.ackedBytesCount += ackedBytes + + if c.epoch.IsZero() { + // First ACK after a loss event. + c.epoch = eventTime // Start of epoch. + c.ackedBytesCount = ackedBytes // Reset count. + // Reset estimated_tcp_congestion_window_ to be in sync with cubic. + c.estimatedTCPcongestionWindow = currentCongestionWindow + if c.lastMaxCongestionWindow <= currentCongestionWindow { + c.timeToOriginPoint = 0 + c.originPointCongestionWindow = currentCongestionWindow + } else { + c.timeToOriginPoint = uint32(math.Cbrt(float64(cubeFactor * (c.lastMaxCongestionWindow - currentCongestionWindow)))) + c.originPointCongestionWindow = c.lastMaxCongestionWindow + } + } + + // Change the time unit from microseconds to 2^10 fractions per second. Take + // the round trip time in account. This is done to allow us to use shift as a + // divide operator. + elapsedTime := int64(eventTime.Add(delayMin).Sub(c.epoch)/time.Microsecond) << 10 / (1000 * 1000) + + // Right-shifts of negative, signed numbers have implementation-dependent + // behavior, so force the offset to be positive, as is done in the kernel. + offset := int64(c.timeToOriginPoint) - elapsedTime + if offset < 0 { + offset = -offset + } + + deltaCongestionWindow := congestion.ByteCount(cubeCongestionWindowScale*offset*offset*offset) * maxDatagramSize >> cubeScale + var targetCongestionWindow congestion.ByteCount + if elapsedTime > int64(c.timeToOriginPoint) { + targetCongestionWindow = c.originPointCongestionWindow + deltaCongestionWindow + } else { + targetCongestionWindow = c.originPointCongestionWindow - deltaCongestionWindow + } + // Limit the CWND increase to half the acked bytes. + targetCongestionWindow = Min(targetCongestionWindow, currentCongestionWindow+c.ackedBytesCount/2) + + // Increase the window by approximately Alpha * 1 MSS of bytes every + // time we ack an estimated tcp window of bytes. For small + // congestion windows (less than 25), the formula below will + // increase slightly slower than linearly per estimated tcp window + // of bytes. + c.estimatedTCPcongestionWindow += congestion.ByteCount(float32(c.ackedBytesCount) * c.alpha() * float32(maxDatagramSize) / float32(c.estimatedTCPcongestionWindow)) + c.ackedBytesCount = 0 + + // We have a new cubic congestion window. + c.lastTargetCongestionWindow = targetCongestionWindow + + // Compute target congestion_window based on cubic target and estimated TCP + // congestion_window, use highest (fastest). + if targetCongestionWindow < c.estimatedTCPcongestionWindow { + targetCongestionWindow = c.estimatedTCPcongestionWindow + } + return targetCongestionWindow +} + +// SetNumConnections sets the number of emulated connections +func (c *Cubic) SetNumConnections(n int) { + c.numConnections = n +} diff --git a/transport/tuic/congestion/cubic_sender.go b/transport/tuic/congestion/cubic_sender.go new file mode 100644 index 00000000..fc97d17a --- /dev/null +++ b/transport/tuic/congestion/cubic_sender.go @@ -0,0 +1,318 @@ +package congestion + +import ( + "fmt" + "time" + + "github.com/sagernet/quic-go/congestion" + "github.com/sagernet/quic-go/logging" +) + +const ( + maxBurstPackets = 3 + renoBeta = 0.7 // Reno backoff factor. + minCongestionWindowPackets = 2 + initialCongestionWindow = 32 +) + +const ( + InvalidPacketNumber congestion.PacketNumber = -1 + MaxCongestionWindowPackets = 20000 + MaxByteCount = congestion.ByteCount(1<<62 - 1) +) + +type cubicSender struct { + hybridSlowStart HybridSlowStart + rttStats congestion.RTTStatsProvider + cubic *Cubic + pacer *pacer + clock Clock + + reno bool + + // Track the largest packet that has been sent. + largestSentPacketNumber congestion.PacketNumber + + // Track the largest packet that has been acked. + largestAckedPacketNumber congestion.PacketNumber + + // Track the largest packet number outstanding when a CWND cutback occurs. + largestSentAtLastCutback congestion.PacketNumber + + // Whether the last loss event caused us to exit slowstart. + // Used for stats collection of slowstartPacketsLost + lastCutbackExitedSlowstart bool + + // Congestion window in bytes. + congestionWindow congestion.ByteCount + + // Slow start congestion window in bytes, aka ssthresh. + slowStartThreshold congestion.ByteCount + + // ACK counter for the Reno implementation. + numAckedPackets uint64 + + initialCongestionWindow congestion.ByteCount + initialMaxCongestionWindow congestion.ByteCount + + maxDatagramSize congestion.ByteCount + + lastState logging.CongestionState + tracer logging.ConnectionTracer +} + +var _ congestion.CongestionControl = &cubicSender{} + +// NewCubicSender makes a new cubic sender +func NewCubicSender( + clock Clock, + initialMaxDatagramSize congestion.ByteCount, + reno bool, + tracer logging.ConnectionTracer, +) *cubicSender { + return newCubicSender( + clock, + reno, + initialMaxDatagramSize, + initialCongestionWindow*initialMaxDatagramSize, + MaxCongestionWindowPackets*initialMaxDatagramSize, + tracer, + ) +} + +func newCubicSender( + clock Clock, + reno bool, + initialMaxDatagramSize, + initialCongestionWindow, + initialMaxCongestionWindow congestion.ByteCount, + tracer logging.ConnectionTracer, +) *cubicSender { + c := &cubicSender{ + largestSentPacketNumber: InvalidPacketNumber, + largestAckedPacketNumber: InvalidPacketNumber, + largestSentAtLastCutback: InvalidPacketNumber, + initialCongestionWindow: initialCongestionWindow, + initialMaxCongestionWindow: initialMaxCongestionWindow, + congestionWindow: initialCongestionWindow, + slowStartThreshold: MaxByteCount, + cubic: NewCubic(clock), + clock: clock, + reno: reno, + tracer: tracer, + maxDatagramSize: initialMaxDatagramSize, + } + c.pacer = newPacer(c.BandwidthEstimate) + if c.tracer != nil { + c.lastState = logging.CongestionStateSlowStart + c.tracer.UpdatedCongestionState(logging.CongestionStateSlowStart) + } + return c +} + +func (c *cubicSender) SetRTTStatsProvider(provider congestion.RTTStatsProvider) { + c.rttStats = provider +} + +// TimeUntilSend returns when the next packet should be sent. +func (c *cubicSender) TimeUntilSend(_ congestion.ByteCount) time.Time { + return c.pacer.TimeUntilSend() +} + +func (c *cubicSender) HasPacingBudget(now time.Time) bool { + return c.pacer.Budget(now) >= c.maxDatagramSize +} + +func (c *cubicSender) maxCongestionWindow() congestion.ByteCount { + return c.maxDatagramSize * MaxCongestionWindowPackets +} + +func (c *cubicSender) minCongestionWindow() congestion.ByteCount { + return c.maxDatagramSize * minCongestionWindowPackets +} + +func (c *cubicSender) OnPacketSent( + sentTime time.Time, + _ congestion.ByteCount, + packetNumber congestion.PacketNumber, + bytes congestion.ByteCount, + isRetransmittable bool, +) { + c.pacer.SentPacket(sentTime, bytes) + if !isRetransmittable { + return + } + c.largestSentPacketNumber = packetNumber + c.hybridSlowStart.OnPacketSent(packetNumber) +} + +func (c *cubicSender) CanSend(bytesInFlight congestion.ByteCount) bool { + return bytesInFlight < c.GetCongestionWindow() +} + +func (c *cubicSender) InRecovery() bool { + return c.largestAckedPacketNumber != InvalidPacketNumber && c.largestAckedPacketNumber <= c.largestSentAtLastCutback +} + +func (c *cubicSender) InSlowStart() bool { + return c.GetCongestionWindow() < c.slowStartThreshold +} + +func (c *cubicSender) GetCongestionWindow() congestion.ByteCount { + return c.congestionWindow +} + +func (c *cubicSender) MaybeExitSlowStart() { + if c.InSlowStart() && + c.hybridSlowStart.ShouldExitSlowStart(c.rttStats.LatestRTT(), c.rttStats.MinRTT(), c.GetCongestionWindow()/c.maxDatagramSize) { + // exit slow start + c.slowStartThreshold = c.congestionWindow + c.maybeTraceStateChange(logging.CongestionStateCongestionAvoidance) + } +} + +func (c *cubicSender) OnPacketAcked( + ackedPacketNumber congestion.PacketNumber, + ackedBytes congestion.ByteCount, + priorInFlight congestion.ByteCount, + eventTime time.Time, +) { + c.largestAckedPacketNumber = Max(ackedPacketNumber, c.largestAckedPacketNumber) + if c.InRecovery() { + return + } + c.maybeIncreaseCwnd(ackedPacketNumber, ackedBytes, priorInFlight, eventTime) + if c.InSlowStart() { + c.hybridSlowStart.OnPacketAcked(ackedPacketNumber) + } +} + +func (c *cubicSender) OnPacketLost(packetNumber congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) { + // TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets + // already sent should be treated as a single loss event, since it's expected. + if packetNumber <= c.largestSentAtLastCutback { + return + } + c.lastCutbackExitedSlowstart = c.InSlowStart() + c.maybeTraceStateChange(logging.CongestionStateRecovery) + + if c.reno { + c.congestionWindow = congestion.ByteCount(float64(c.congestionWindow) * renoBeta) + } else { + c.congestionWindow = c.cubic.CongestionWindowAfterPacketLoss(c.congestionWindow) + } + if minCwnd := c.minCongestionWindow(); c.congestionWindow < minCwnd { + c.congestionWindow = minCwnd + } + c.slowStartThreshold = c.congestionWindow + c.largestSentAtLastCutback = c.largestSentPacketNumber + // reset packet count from congestion avoidance mode. We start + // counting again when we're out of recovery. + c.numAckedPackets = 0 +} + +// Called when we receive an ack. Normal TCP tracks how many packets one ack +// represents, but quic has a separate ack for each packet. +func (c *cubicSender) maybeIncreaseCwnd( + _ congestion.PacketNumber, + ackedBytes congestion.ByteCount, + priorInFlight congestion.ByteCount, + eventTime time.Time, +) { + // Do not increase the congestion window unless the sender is close to using + // the current window. + if !c.isCwndLimited(priorInFlight) { + c.cubic.OnApplicationLimited() + c.maybeTraceStateChange(logging.CongestionStateApplicationLimited) + return + } + if c.congestionWindow >= c.maxCongestionWindow() { + return + } + if c.InSlowStart() { + // TCP slow start, exponential growth, increase by one for each ACK. + c.congestionWindow += c.maxDatagramSize + c.maybeTraceStateChange(logging.CongestionStateSlowStart) + return + } + // Congestion avoidance + c.maybeTraceStateChange(logging.CongestionStateCongestionAvoidance) + if c.reno { + // Classic Reno congestion avoidance. + c.numAckedPackets++ + if c.numAckedPackets >= uint64(c.congestionWindow/c.maxDatagramSize) { + c.congestionWindow += c.maxDatagramSize + c.numAckedPackets = 0 + } + } else { + c.congestionWindow = Min(c.maxCongestionWindow(), c.cubic.CongestionWindowAfterAck(ackedBytes, c.congestionWindow, c.rttStats.MinRTT(), eventTime)) + } +} + +func (c *cubicSender) isCwndLimited(bytesInFlight congestion.ByteCount) bool { + congestionWindow := c.GetCongestionWindow() + if bytesInFlight >= congestionWindow { + return true + } + availableBytes := congestionWindow - bytesInFlight + slowStartLimited := c.InSlowStart() && bytesInFlight > congestionWindow/2 + return slowStartLimited || availableBytes <= maxBurstPackets*c.maxDatagramSize +} + +// BandwidthEstimate returns the current bandwidth estimate +func (c *cubicSender) BandwidthEstimate() Bandwidth { + if c.rttStats == nil { + return infBandwidth + } + srtt := c.rttStats.SmoothedRTT() + if srtt == 0 { + // If we haven't measured an rtt, the bandwidth estimate is unknown. + return infBandwidth + } + return BandwidthFromDelta(c.GetCongestionWindow(), srtt) +} + +// OnRetransmissionTimeout is called on an retransmission timeout +func (c *cubicSender) OnRetransmissionTimeout(packetsRetransmitted bool) { + c.largestSentAtLastCutback = InvalidPacketNumber + if !packetsRetransmitted { + return + } + c.hybridSlowStart.Restart() + c.cubic.Reset() + c.slowStartThreshold = c.congestionWindow / 2 + c.congestionWindow = c.minCongestionWindow() +} + +// OnConnectionMigration is called when the connection is migrated (?) +func (c *cubicSender) OnConnectionMigration() { + c.hybridSlowStart.Restart() + c.largestSentPacketNumber = InvalidPacketNumber + c.largestAckedPacketNumber = InvalidPacketNumber + c.largestSentAtLastCutback = InvalidPacketNumber + c.lastCutbackExitedSlowstart = false + c.cubic.Reset() + c.numAckedPackets = 0 + c.congestionWindow = c.initialCongestionWindow + c.slowStartThreshold = c.initialMaxCongestionWindow +} + +func (c *cubicSender) maybeTraceStateChange(new logging.CongestionState) { + if c.tracer == nil || new == c.lastState { + return + } + c.tracer.UpdatedCongestionState(new) + c.lastState = new +} + +func (c *cubicSender) SetMaxDatagramSize(s congestion.ByteCount) { + if s < c.maxDatagramSize { + panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", c.maxDatagramSize, s)) + } + cwndIsMinCwnd := c.congestionWindow == c.minCongestionWindow() + c.maxDatagramSize = s + if cwndIsMinCwnd { + c.congestionWindow = c.minCongestionWindow() + } + c.pacer.SetMaxDatagramSize(s) +} diff --git a/transport/tuic/congestion/hybrid_slow_start.go b/transport/tuic/congestion/hybrid_slow_start.go new file mode 100644 index 00000000..eba8f7df --- /dev/null +++ b/transport/tuic/congestion/hybrid_slow_start.go @@ -0,0 +1,112 @@ +package congestion + +import ( + "time" + + "github.com/sagernet/quic-go/congestion" +) + +// Note(pwestin): the magic clamping numbers come from the original code in +// tcp_cubic.c. +const hybridStartLowWindow = congestion.ByteCount(16) + +// Number of delay samples for detecting the increase of delay. +const hybridStartMinSamples = uint32(8) + +// Exit slow start if the min rtt has increased by more than 1/8th. +const hybridStartDelayFactorExp = 3 // 2^3 = 8 +// The original paper specifies 2 and 8ms, but those have changed over time. +const ( + hybridStartDelayMinThresholdUs = int64(4000) + hybridStartDelayMaxThresholdUs = int64(16000) +) + +// HybridSlowStart implements the TCP hybrid slow start algorithm +type HybridSlowStart struct { + endPacketNumber congestion.PacketNumber + lastSentPacketNumber congestion.PacketNumber + started bool + currentMinRTT time.Duration + rttSampleCount uint32 + hystartFound bool +} + +// StartReceiveRound is called for the start of each receive round (burst) in the slow start phase. +func (s *HybridSlowStart) StartReceiveRound(lastSent congestion.PacketNumber) { + s.endPacketNumber = lastSent + s.currentMinRTT = 0 + s.rttSampleCount = 0 + s.started = true +} + +// IsEndOfRound returns true if this ack is the last packet number of our current slow start round. +func (s *HybridSlowStart) IsEndOfRound(ack congestion.PacketNumber) bool { + return s.endPacketNumber < ack +} + +// ShouldExitSlowStart should be called on every new ack frame, since a new +// RTT measurement can be made then. +// rtt: the RTT for this ack packet. +// minRTT: is the lowest delay (RTT) we have seen during the session. +// congestionWindow: the congestion window in packets. +func (s *HybridSlowStart) ShouldExitSlowStart(latestRTT time.Duration, minRTT time.Duration, congestionWindow congestion.ByteCount) bool { + if !s.started { + // Time to start the hybrid slow start. + s.StartReceiveRound(s.lastSentPacketNumber) + } + if s.hystartFound { + return true + } + // Second detection parameter - delay increase detection. + // Compare the minimum delay (s.currentMinRTT) of the current + // burst of packets relative to the minimum delay during the session. + // Note: we only look at the first few(8) packets in each burst, since we + // only want to compare the lowest RTT of the burst relative to previous + // bursts. + s.rttSampleCount++ + if s.rttSampleCount <= hybridStartMinSamples { + if s.currentMinRTT == 0 || s.currentMinRTT > latestRTT { + s.currentMinRTT = latestRTT + } + } + // We only need to check this once per round. + if s.rttSampleCount == hybridStartMinSamples { + // Divide minRTT by 8 to get a rtt increase threshold for exiting. + minRTTincreaseThresholdUs := int64(minRTT / time.Microsecond >> hybridStartDelayFactorExp) + // Ensure the rtt threshold is never less than 2ms or more than 16ms. + minRTTincreaseThresholdUs = Min(minRTTincreaseThresholdUs, hybridStartDelayMaxThresholdUs) + minRTTincreaseThreshold := time.Duration(Max(minRTTincreaseThresholdUs, hybridStartDelayMinThresholdUs)) * time.Microsecond + + if s.currentMinRTT > (minRTT + minRTTincreaseThreshold) { + s.hystartFound = true + } + } + // Exit from slow start if the cwnd is greater than 16 and + // increasing delay is found. + return congestionWindow >= hybridStartLowWindow && s.hystartFound +} + +// OnPacketSent is called when a packet was sent +func (s *HybridSlowStart) OnPacketSent(packetNumber congestion.PacketNumber) { + s.lastSentPacketNumber = packetNumber +} + +// OnPacketAcked gets invoked after ShouldExitSlowStart, so it's best to end +// the round when the final packet of the burst is received and start it on +// the next incoming ack. +func (s *HybridSlowStart) OnPacketAcked(ackedPacketNumber congestion.PacketNumber) { + if s.IsEndOfRound(ackedPacketNumber) { + s.started = false + } +} + +// Started returns true if started +func (s *HybridSlowStart) Started() bool { + return s.started +} + +// Restart the slow start phase +func (s *HybridSlowStart) Restart() { + s.started = false + s.hystartFound = false +} diff --git a/transport/tuic/congestion/minmax.go b/transport/tuic/congestion/minmax.go new file mode 100644 index 00000000..ed75072e --- /dev/null +++ b/transport/tuic/congestion/minmax.go @@ -0,0 +1,72 @@ +package congestion + +import ( + "math" + "time" + + "golang.org/x/exp/constraints" +) + +// InfDuration is a duration of infinite length +const InfDuration = time.Duration(math.MaxInt64) + +func Max[T constraints.Ordered](a, b T) T { + if a < b { + return b + } + return a +} + +func Min[T constraints.Ordered](a, b T) T { + if a < b { + return a + } + return b +} + +// MinNonZeroDuration return the minimum duration that's not zero. +func MinNonZeroDuration(a, b time.Duration) time.Duration { + if a == 0 { + return b + } + if b == 0 { + return a + } + return Min(a, b) +} + +// AbsDuration returns the absolute value of a time duration +func AbsDuration(d time.Duration) time.Duration { + if d >= 0 { + return d + } + return -d +} + +// MinTime returns the earlier time +func MinTime(a, b time.Time) time.Time { + if a.After(b) { + return b + } + return a +} + +// MinNonZeroTime returns the earlist time that is not time.Time{} +// If both a and b are time.Time{}, it returns time.Time{} +func MinNonZeroTime(a, b time.Time) time.Time { + if a.IsZero() { + return b + } + if b.IsZero() { + return a + } + return MinTime(a, b) +} + +// MaxTime returns the later time +func MaxTime(a, b time.Time) time.Time { + if a.After(b) { + return a + } + return b +} diff --git a/transport/tuic/congestion/pacer.go b/transport/tuic/congestion/pacer.go new file mode 100644 index 00000000..5d0f13f6 --- /dev/null +++ b/transport/tuic/congestion/pacer.go @@ -0,0 +1,81 @@ +package congestion + +import ( + "math" + "time" + + "github.com/sagernet/quic-go/congestion" +) + +const ( + initialMaxDatagramSize = congestion.ByteCount(1252) + MinPacingDelay = time.Millisecond + TimerGranularity = time.Millisecond + maxBurstSizePackets = 10 +) + +// The pacer implements a token bucket pacing algorithm. +type pacer struct { + budgetAtLastSent congestion.ByteCount + maxDatagramSize congestion.ByteCount + lastSentTime time.Time + getAdjustedBandwidth func() uint64 // in bytes/s +} + +func newPacer(getBandwidth func() Bandwidth) *pacer { + p := &pacer{ + maxDatagramSize: initialMaxDatagramSize, + getAdjustedBandwidth: func() uint64 { + // Bandwidth is in bits/s. We need the value in bytes/s. + bw := uint64(getBandwidth() / BytesPerSecond) + // Use a slightly higher value than the actual measured bandwidth. + // RTT variations then won't result in under-utilization of the congestion window. + // Ultimately, this will result in sending packets as acknowledgments are received rather than when timers fire, + // provided the congestion window is fully utilized and acknowledgments arrive at regular intervals. + return bw * 5 / 4 + }, + } + p.budgetAtLastSent = p.maxBurstSize() + return p +} + +func (p *pacer) SentPacket(sendTime time.Time, size congestion.ByteCount) { + budget := p.Budget(sendTime) + if size > budget { + p.budgetAtLastSent = 0 + } else { + p.budgetAtLastSent = budget - size + } + p.lastSentTime = sendTime +} + +func (p *pacer) Budget(now time.Time) congestion.ByteCount { + if p.lastSentTime.IsZero() { + return p.maxBurstSize() + } + budget := p.budgetAtLastSent + (congestion.ByteCount(p.getAdjustedBandwidth())*congestion.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9 + return Min(p.maxBurstSize(), budget) +} + +func (p *pacer) maxBurstSize() congestion.ByteCount { + return Max( + congestion.ByteCount(uint64((MinPacingDelay+TimerGranularity).Nanoseconds())*p.getAdjustedBandwidth())/1e9, + maxBurstSizePackets*p.maxDatagramSize, + ) +} + +// TimeUntilSend returns when the next packet should be sent. +// It returns the zero value of time.Time if a packet can be sent immediately. +func (p *pacer) TimeUntilSend() time.Time { + if p.budgetAtLastSent >= p.maxDatagramSize { + return time.Time{} + } + return p.lastSentTime.Add(Max( + MinPacingDelay, + time.Duration(math.Ceil(float64(p.maxDatagramSize-p.budgetAtLastSent)*1e9/float64(p.getAdjustedBandwidth())))*time.Nanosecond, + )) +} + +func (p *pacer) SetMaxDatagramSize(s congestion.ByteCount) { + p.maxDatagramSize = s +} diff --git a/transport/tuic/congestion/windowed_filter.go b/transport/tuic/congestion/windowed_filter.go new file mode 100644 index 00000000..4da595b9 --- /dev/null +++ b/transport/tuic/congestion/windowed_filter.go @@ -0,0 +1,132 @@ +package congestion + +// WindowedFilter Use the following to construct a windowed filter object of type T. +// For example, a min filter using QuicTime as the time type: +// +// WindowedFilter, QuicTime, QuicTime::Delta> ObjectName; +// +// A max filter using 64-bit integers as the time type: +// +// WindowedFilter, uint64_t, int64_t> ObjectName; +// +// Specifically, this template takes four arguments: +// 1. T -- type of the measurement that is being filtered. +// 2. Compare -- MinFilter or MaxFilter, depending on the type of filter +// desired. +// 3. TimeT -- the type used to represent timestamps. +// 4. TimeDeltaT -- the type used to represent continuous time intervals between +// two timestamps. Has to be the type of (a - b) if both |a| and |b| are +// of type TimeT. +type WindowedFilter struct { + // Time length of window. + windowLength int64 + estimates []Sample + comparator func(int64, int64) bool +} + +type Sample struct { + sample int64 + time int64 +} + +// Compares two values and returns true if the first is greater than or equal +// to the second. +func MaxFilter(a, b int64) bool { + return a >= b +} + +// Compares two values and returns true if the first is less than or equal +// to the second. +func MinFilter(a, b int64) bool { + return a <= b +} + +func NewWindowedFilter(windowLength int64, comparator func(int64, int64) bool) *WindowedFilter { + return &WindowedFilter{ + windowLength: windowLength, + estimates: make([]Sample, 3), + comparator: comparator, + } +} + +// Changes the window length. Does not update any current samples. +func (f *WindowedFilter) SetWindowLength(windowLength int64) { + f.windowLength = windowLength +} + +func (f *WindowedFilter) GetBest() int64 { + return f.estimates[0].sample +} + +func (f *WindowedFilter) GetSecondBest() int64 { + return f.estimates[1].sample +} + +func (f *WindowedFilter) GetThirdBest() int64 { + return f.estimates[2].sample +} + +func (f *WindowedFilter) Update(sample int64, time int64) { + if f.estimates[0].time == 0 || f.comparator(sample, f.estimates[0].sample) || (time-f.estimates[2].time) > f.windowLength { + f.Reset(sample, time) + return + } + + if f.comparator(sample, f.estimates[1].sample) { + f.estimates[1].sample = sample + f.estimates[1].time = time + f.estimates[2].sample = sample + f.estimates[2].time = time + } else if f.comparator(sample, f.estimates[2].sample) { + f.estimates[2].sample = sample + f.estimates[2].time = time + } + + // Expire and update estimates as necessary. + if time-f.estimates[0].time > f.windowLength { + // The best estimate hasn't been updated for an entire window, so promote + // second and third best estimates. + f.estimates[0].sample = f.estimates[1].sample + f.estimates[0].time = f.estimates[1].time + f.estimates[1].sample = f.estimates[2].sample + f.estimates[1].time = f.estimates[2].time + f.estimates[2].sample = sample + f.estimates[2].time = time + // Need to iterate one more time. Check if the new best estimate is + // outside the window as well, since it may also have been recorded a + // long time ago. Don't need to iterate once more since we cover that + // case at the beginning of the method. + if time-f.estimates[0].time > f.windowLength { + f.estimates[0].sample = f.estimates[1].sample + f.estimates[0].time = f.estimates[1].time + f.estimates[1].sample = f.estimates[2].sample + f.estimates[1].time = f.estimates[2].time + } + return + } + if f.estimates[1].sample == f.estimates[0].sample && time-f.estimates[1].time > f.windowLength>>2 { + // A quarter of the window has passed without a better sample, so the + // second-best estimate is taken from the second quarter of the window. + f.estimates[1].sample = sample + f.estimates[1].time = time + f.estimates[2].sample = sample + f.estimates[2].time = time + return + } + + if f.estimates[2].sample == f.estimates[1].sample && time-f.estimates[2].time > f.windowLength>>1 { + // We've passed a half of the window without a better estimate, so take + // a third-best estimate from the second half of the window. + f.estimates[2].sample = sample + f.estimates[2].time = time + } +} + +func (f *WindowedFilter) Reset(newSample int64, newTime int64) { + f.estimates[0].sample = newSample + f.estimates[0].time = newTime + f.estimates[1].sample = newSample + f.estimates[1].time = newTime + f.estimates[2].sample = newSample + f.estimates[2].time = newTime +} diff --git a/transport/tuic/packet.go b/transport/tuic/packet.go new file mode 100644 index 00000000..a3a0c35a --- /dev/null +++ b/transport/tuic/packet.go @@ -0,0 +1,497 @@ +package tuic + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "io" + "math" + "net" + "os" + "sync" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/cache" + M "github.com/sagernet/sing/common/metadata" +) + +var udpMessagePool = sync.Pool{ + New: func() interface{} { + return new(udpMessage) + }, +} + +func releaseMessages(messages []*udpMessage) { + for _, message := range messages { + if message != nil { + *message = udpMessage{} + udpMessagePool.Put(message) + } + } +} + +type udpMessage struct { + sessionID uint16 + packetID uint16 + fragmentTotal uint8 + fragmentID uint8 + destination M.Socksaddr + dataLength uint16 + data *buf.Buffer +} + +func (m *udpMessage) release() { + *m = udpMessage{} + udpMessagePool.Put(m) +} + +func (m *udpMessage) releaseMessage() { + m.data.Release() + m.release() +} + +func (m *udpMessage) pack() *buf.Buffer { + buffer := buf.NewSize(m.headerSize() + m.data.Len()) + common.Must( + buffer.WriteByte(Version), + buffer.WriteByte(CommandPacket), + binary.Write(buffer, binary.BigEndian, m.sessionID), + binary.Write(buffer, binary.BigEndian, m.packetID), + binary.Write(buffer, binary.BigEndian, m.fragmentTotal), + binary.Write(buffer, binary.BigEndian, m.fragmentID), + binary.Write(buffer, binary.BigEndian, uint16(m.data.Len())), + addressSerializer.WriteAddrPort(buffer, m.destination), + common.Error(buffer.Write(m.data.Bytes())), + ) + return buffer +} + +func (m *udpMessage) headerSize() int { + return 2 + 10 + addressSerializer.AddrPortLen(m.destination) +} + +func fragUDPMessage(message *udpMessage, maxPacketSize int) []*udpMessage { + if message.data.Len() <= maxPacketSize { + return []*udpMessage{message} + } + var fragments []*udpMessage + originPacket := message.data.Bytes() + udpMTU := maxPacketSize - message.headerSize() + for remaining := len(originPacket); remaining > 0; remaining -= udpMTU { + fragment := udpMessagePool.Get().(*udpMessage) + *fragment = *message + if remaining > udpMTU { + fragment.data = buf.As(originPacket[:udpMTU]) + originPacket = originPacket[udpMTU:] + } else { + fragment.data = buf.As(originPacket) + originPacket = nil + } + fragments = append(fragments, fragment) + } + fragmentTotal := uint16(len(fragments)) + for index, fragment := range fragments { + fragment.fragmentID = uint8(index) + fragment.fragmentTotal = uint8(fragmentTotal) + if index > 0 { + fragment.destination = M.Socksaddr{} + } + } + return fragments +} + +type udpPacketConn struct { + ctx context.Context + cancel common.ContextCancelCauseFunc + sessionID uint16 + quicConn quic.Connection + data chan *udpMessage + udpStream bool + udpMTU int + packetId atomic.Uint32 + closeOnce sync.Once + isServer bool + defragger *udpDefragger + onDestroy func() +} + +func newUDPPacketConn(ctx context.Context, quicConn quic.Connection, udpStream bool, isServer bool, onDestroy func()) *udpPacketConn { + ctx, cancel := common.ContextWithCancelCause(ctx) + return &udpPacketConn{ + ctx: ctx, + cancel: cancel, + quicConn: quicConn, + data: make(chan *udpMessage, 64), + udpStream: udpStream, + isServer: isServer, + defragger: newUDPDefragger(), + onDestroy: onDestroy, + } +} + +func (c *udpPacketConn) ReadPacketThreadSafe() (buffer *buf.Buffer, destination M.Socksaddr, err error) { + select { + case p := <-c.data: + buffer = p.data + destination = p.destination + p.release() + return + case <-c.ctx.Done(): + return nil, M.Socksaddr{}, io.ErrClosedPipe + } +} + +func (c *udpPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + select { + case p := <-c.data: + _, err = buffer.ReadOnceFrom(p.data) + destination = p.destination + p.releaseMessage() + return + case <-c.ctx.Done(): + return M.Socksaddr{}, io.ErrClosedPipe + } +} + +func (c *udpPacketConn) WaitReadPacket(newBuffer func() *buf.Buffer) (destination M.Socksaddr, err error) { + select { + case p := <-c.data: + _, err = newBuffer().ReadOnceFrom(p.data) + destination = p.destination + p.releaseMessage() + return + case <-c.ctx.Done(): + return M.Socksaddr{}, io.ErrClosedPipe + } +} + +func (c *udpPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + select { + case pkt := <-c.data: + n = copy(p, pkt.data.Bytes()) + if pkt.destination.IsFqdn() { + addr = pkt.destination + } else { + addr = pkt.destination.UDPAddr() + } + pkt.releaseMessage() + return n, addr, nil + case <-c.ctx.Done(): + return 0, nil, io.ErrClosedPipe + } +} + +func (c *udpPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + defer buffer.Release() + select { + case <-c.ctx.Done(): + return net.ErrClosed + default: + } + if buffer.Len() > 0xffff { + return quic.ErrMessageTooLarge(0xffff) + } + packetId := c.packetId.Add(1) + if packetId > math.MaxUint16 { + c.packetId.Store(0) + packetId = 0 + } + message := udpMessagePool.Get().(*udpMessage) + *message = udpMessage{ + sessionID: c.sessionID, + packetID: uint16(packetId), + fragmentTotal: 1, + destination: destination, + data: buffer, + } + defer message.releaseMessage() + var err error + if !c.udpStream && c.udpMTU > 0 && buffer.Len() > c.udpMTU { + err = c.writePackets(fragUDPMessage(message, c.udpMTU)) + } else { + err = c.writePacket(message) + } + if err == nil { + return nil + } + var tooLargeErr quic.ErrMessageTooLarge + if !errors.As(err, &tooLargeErr) { + return err + } + c.udpMTU = int(tooLargeErr) + return c.writePackets(fragUDPMessage(message, c.udpMTU)) +} + +func (c *udpPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + select { + case <-c.ctx.Done(): + return 0, net.ErrClosed + default: + } + if len(p) > 0xffff { + return 0, quic.ErrMessageTooLarge(0xffff) + } + packetId := c.packetId.Add(1) + if packetId > math.MaxUint16 { + c.packetId.Store(0) + packetId = 0 + } + message := udpMessagePool.Get().(*udpMessage) + *message = udpMessage{ + sessionID: c.sessionID, + packetID: uint16(packetId), + fragmentTotal: 1, + destination: M.SocksaddrFromNet(addr), + data: buf.As(p), + } + if c.udpMTU > 0 && len(p) > c.udpMTU { + err = c.writePackets(fragUDPMessage(message, c.udpMTU)) + if err == nil { + return len(p), nil + } + } else { + err = c.writePacket(message) + } + if err == nil { + return len(p), nil + } + var tooLargeErr quic.ErrMessageTooLarge + if !errors.As(err, &tooLargeErr) { + return + } + c.udpMTU = int(tooLargeErr) + err = c.writePackets(fragUDPMessage(message, c.udpMTU)) + if err == nil { + return len(p), nil + } + return +} + +func (c *udpPacketConn) inputPacket(message *udpMessage) { + if message.fragmentTotal <= 1 { + select { + case c.data <- message: + default: + } + } else { + newMessage := c.defragger.feed(message) + if newMessage != nil { + select { + case c.data <- newMessage: + default: + } + } + } +} + +func (c *udpPacketConn) writePackets(messages []*udpMessage) error { + defer releaseMessages(messages) + for _, message := range messages { + err := c.writePacket(message) + if err != nil { + return err + } + } + return nil +} + +func (c *udpPacketConn) writePacket(message *udpMessage) error { + if !c.udpStream { + buffer := message.pack() + err := c.quicConn.SendMessage(buffer.Bytes()) + buffer.Release() + if err != nil { + return err + } + } else { + stream, err := c.quicConn.OpenUniStream() + if err != nil { + return err + } + buffer := message.pack() + _, err = stream.Write(buffer.Bytes()) + buffer.Release() + stream.Close() + if err != nil { + return err + } + } + return nil +} + +func (c *udpPacketConn) Close() error { + c.closeOnce.Do(func() { + c.closeWithError(os.ErrClosed) + c.onDestroy() + }) + return nil +} + +func (c *udpPacketConn) closeWithError(err error) { + c.cancel(err) + if !c.isServer { + buffer := buf.NewSize(4) + defer buffer.Release() + buffer.WriteByte(Version) + buffer.WriteByte(CommandDissociate) + binary.Write(buffer, binary.BigEndian, c.sessionID) + sendStream, openErr := c.quicConn.OpenUniStream() + if openErr != nil { + return + } + defer sendStream.Close() + sendStream.Write(buffer.Bytes()) + } +} + +func (c *udpPacketConn) LocalAddr() net.Addr { + return c.quicConn.LocalAddr() +} + +func (c *udpPacketConn) SetDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (c *udpPacketConn) SetReadDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (c *udpPacketConn) SetWriteDeadline(t time.Time) error { + return os.ErrInvalid +} + +type udpDefragger struct { + packetMap *cache.LruCache[uint16, *packetItem] +} + +func newUDPDefragger() *udpDefragger { + return &udpDefragger{ + packetMap: cache.New( + cache.WithAge[uint16, *packetItem](10), + cache.WithUpdateAgeOnGet[uint16, *packetItem](), + cache.WithEvict[uint16, *packetItem](func(key uint16, value *packetItem) { + releaseMessages(value.messages) + }), + ), + } +} + +type packetItem struct { + access sync.Mutex + messages []*udpMessage + count uint8 +} + +func (d *udpDefragger) feed(m *udpMessage) *udpMessage { + if m.fragmentTotal <= 1 { + return m + } + if m.fragmentID >= m.fragmentTotal { + return nil + } + item, _ := d.packetMap.LoadOrStore(m.packetID, newPacketItem) + item.access.Lock() + defer item.access.Unlock() + if int(m.fragmentTotal) != len(item.messages) { + releaseMessages(item.messages) + item.messages = make([]*udpMessage, m.fragmentTotal) + item.count = 1 + item.messages[m.fragmentID] = m + return nil + } + if item.messages[m.fragmentID] != nil { + return nil + } + item.messages[m.fragmentID] = m + item.count++ + if int(item.count) != len(item.messages) { + return nil + } + newMessage := udpMessagePool.Get().(*udpMessage) + *newMessage = *item.messages[0] + if m.dataLength > 0 { + newMessage.data = buf.NewSize(int(m.dataLength)) + for _, message := range item.messages { + newMessage.data.Write(message.data.Bytes()) + message.releaseMessage() + } + item.messages = nil + return newMessage + } + return nil +} + +func newPacketItem() *packetItem { + return new(packetItem) +} + +func readUDPMessage(message *udpMessage, reader io.Reader) error { + err := binary.Read(reader, binary.BigEndian, &message.sessionID) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.packetID) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.fragmentTotal) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.fragmentID) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.dataLength) + if err != nil { + return err + } + message.destination, err = addressSerializer.ReadAddrPort(reader) + if err != nil { + return err + } + message.data = buf.NewSize(int(message.dataLength)) + _, err = message.data.ReadFullFrom(reader, message.data.FreeLen()) + if err != nil { + return err + } + return nil +} + +func decodeUDPMessage(message *udpMessage, data []byte) error { + reader := bytes.NewReader(data) + err := binary.Read(reader, binary.BigEndian, &message.sessionID) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.packetID) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.fragmentTotal) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.fragmentID) + if err != nil { + return err + } + err = binary.Read(reader, binary.BigEndian, &message.dataLength) + if err != nil { + return err + } + message.destination, err = addressSerializer.ReadAddrPort(reader) + if err != nil { + return err + } + if reader.Len() != int(message.dataLength) { + return io.ErrUnexpectedEOF + } + message.data = buf.As(data[len(data)-reader.Len():]) + return nil +} diff --git a/transport/tuic/protocol.go b/transport/tuic/protocol.go new file mode 100644 index 00000000..1247516b --- /dev/null +++ b/transport/tuic/protocol.go @@ -0,0 +1,15 @@ +package tuic + +const ( + Version = 5 +) + +const ( + CommandAuthenticate = iota + CommandConnect + CommandPacket + CommandDissociate + CommandHeartbeat +) + +const AuthenticateLen = 2 + 16 + 32 diff --git a/transport/tuic/server.go b/transport/tuic/server.go new file mode 100644 index 00000000..4a40b44f --- /dev/null +++ b/transport/tuic/server.go @@ -0,0 +1,434 @@ +package tuic + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/binary" + "io" + "net" + "runtime" + "strings" + "sync" + "time" + + "github.com/sagernet/quic-go" + "github.com/sagernet/sing-box/common/baderror" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/gofrs/uuid/v5" +) + +type ServerOptions struct { + Context context.Context + Logger logger.Logger + TLSConfig *tls.Config + Users []User + CongestionControl string + AuthTimeout time.Duration + ZeroRTTHandshake bool + Heartbeat time.Duration + Handler ServerHandler +} + +type User struct { + Name string + UUID uuid.UUID + Password string +} + +type ServerHandler interface { + N.TCPConnectionHandler + N.UDPConnectionHandler +} + +type Server struct { + ctx context.Context + logger logger.Logger + tlsConfig *tls.Config + heartbeat time.Duration + quicConfig *quic.Config + userMap map[uuid.UUID]User + congestionControl string + authTimeout time.Duration + handler ServerHandler + + quicListener io.Closer +} + +func NewServer(options ServerOptions) (*Server, error) { + if options.AuthTimeout == 0 { + options.AuthTimeout = 3 * time.Second + } + if options.Heartbeat == 0 { + options.Heartbeat = 10 * time.Second + } + quicConfig := &quic.Config{ + DisablePathMTUDiscovery: !(runtime.GOOS == "windows" || runtime.GOOS == "linux" || runtime.GOOS == "android" || runtime.GOOS == "darwin"), + MaxDatagramFrameSize: 1400, + EnableDatagrams: true, + Allow0RTT: options.ZeroRTTHandshake, + MaxIncomingStreams: 1 << 60, + MaxIncomingUniStreams: 1 << 60, + } + switch options.CongestionControl { + case "": + options.CongestionControl = "cubic" + case "cubic", "new_reno", "bbr": + default: + return nil, E.New("unknown congestion control algorithm: ", options.CongestionControl) + } + if len(options.Users) == 0 { + return nil, E.New("missing users") + } + userMap := make(map[uuid.UUID]User) + for _, user := range options.Users { + userMap[user.UUID] = user + } + return &Server{ + ctx: options.Context, + logger: options.Logger, + tlsConfig: options.TLSConfig, + heartbeat: options.Heartbeat, + quicConfig: quicConfig, + userMap: userMap, + congestionControl: options.CongestionControl, + authTimeout: options.AuthTimeout, + handler: options.Handler, + }, nil +} + +func (s *Server) Start(conn net.PacketConn) error { + if !s.quicConfig.Allow0RTT { + listener, err := quic.Listen(conn, s.tlsConfig, s.quicConfig) + if err != nil { + return err + } + s.quicListener = listener + go func() { + for { + connection, hErr := listener.Accept(s.ctx) + if hErr != nil { + if strings.Contains(hErr.Error(), "server closed") { + s.logger.Debug(E.Cause(hErr, "listener closed")) + } else { + s.logger.Error(E.Cause(hErr, "listener closed")) + } + return + } + go s.handleConnection(connection) + } + }() + } else { + listener, err := quic.ListenEarly(conn, s.tlsConfig, s.quicConfig) + if err != nil { + return err + } + s.quicListener = listener + go func() { + for { + connection, hErr := listener.Accept(s.ctx) + if hErr != nil { + if strings.Contains(hErr.Error(), "server closed") { + s.logger.Debug(E.Cause(hErr, "listener closed")) + } else { + s.logger.Error(E.Cause(hErr, "listener closed")) + } + return + } + go s.handleConnection(connection) + } + }() + } + return nil +} + +func (s *Server) Close() error { + return common.Close( + s.quicListener, + ) +} + +func (s *Server) handleConnection(connection quic.Connection) { + setCongestion(s.ctx, connection, s.congestionControl) + session := &serverSession{ + Server: s, + ctx: s.ctx, + quicConn: connection, + source: M.SocksaddrFromNet(connection.RemoteAddr()), + connDone: make(chan struct{}), + authDone: make(chan struct{}), + udpConnMap: make(map[uint16]*udpPacketConn), + } + session.handle() +} + +type serverSession struct { + *Server + ctx context.Context + quicConn quic.Connection + source M.Socksaddr + connAccess sync.Mutex + connDone chan struct{} + connErr error + authDone chan struct{} + authUser *User + udpAccess sync.RWMutex + udpConnMap map[uint16]*udpPacketConn +} + +func (s *serverSession) handle() { + if s.ctx.Done() != nil { + go func() { + select { + case <-s.ctx.Done(): + s.closeWithError(s.ctx.Err()) + case <-s.connDone: + } + }() + } + go s.loopUniStreams() + go s.loopStreams() + go s.loopMessages() + go s.handleAuthTimeout() + go s.loopHeartbeats() +} + +func (s *serverSession) loopUniStreams() { + for { + uniStream, err := s.quicConn.AcceptUniStream(s.ctx) + if err != nil { + return + } + go func() { + err = s.handleUniStream(uniStream) + if err != nil { + s.closeWithError(E.Cause(err, "handle uni stream")) + } + }() + } +} + +func (s *serverSession) handleUniStream(stream quic.ReceiveStream) error { + defer stream.CancelRead(0) + buffer := buf.New() + defer buffer.Release() + _, err := buffer.ReadAtLeastFrom(stream, 2) + if err != nil { + return E.Cause(err, "read request") + } + version := buffer.Byte(0) + if version != Version { + return E.New("unknown version ", buffer.Byte(0)) + } + command := buffer.Byte(1) + switch command { + case CommandAuthenticate: + select { + case <-s.authDone: + return E.New("authentication: multiple authentication requests") + default: + } + if buffer.Len() < AuthenticateLen { + _, err = buffer.ReadFullFrom(stream, AuthenticateLen-buffer.Len()) + if err != nil { + return E.Cause(err, "authentication: read request") + } + } + userUUID := uuid.FromBytesOrNil(buffer.Range(2, 2+16)) + user, loaded := s.userMap[userUUID] + if !loaded { + return E.New("authentication: unknown user ", userUUID) + } + handshakeState := s.quicConn.ConnectionState().TLS + tuicToken, err := handshakeState.ExportKeyingMaterial(string(user.UUID[:]), []byte(user.Password), 32) + if err != nil { + return E.Cause(err, "authentication: export keying material") + } + if !bytes.Equal(tuicToken, buffer.Range(2+16, 2+16+32)) { + return E.New("authentication: token mismatch") + } + s.authUser = &user + close(s.authDone) + return nil + case CommandPacket: + select { + case <-s.connDone: + return s.connErr + case <-s.authDone: + } + message := udpMessagePool.Get().(*udpMessage) + err = readUDPMessage(message, io.MultiReader(bytes.NewReader(buffer.From(2)), stream)) + if err != nil { + message.release() + return err + } + s.handleUDPMessage(message, true) + return nil + case CommandDissociate: + select { + case <-s.connDone: + return s.connErr + case <-s.authDone: + } + if buffer.Len() > 4 { + return E.New("invalid dissociate message") + } + var sessionID uint16 + err = binary.Read(io.MultiReader(bytes.NewReader(buffer.From(2)), stream), binary.BigEndian, &sessionID) + if err != nil { + return err + } + s.udpAccess.RLock() + udpConn, loaded := s.udpConnMap[sessionID] + s.udpAccess.RUnlock() + if loaded { + udpConn.closeWithError(E.New("remote closed")) + s.udpAccess.Lock() + delete(s.udpConnMap, sessionID) + s.udpAccess.Unlock() + } + return nil + default: + return E.New("unknown command ", command) + } +} + +func (s *serverSession) handleAuthTimeout() { + select { + case <-s.connDone: + case <-s.authDone: + case <-time.After(s.authTimeout): + s.closeWithError(E.New("authentication timeout")) + } +} + +func (s *serverSession) loopStreams() { + for { + stream, err := s.quicConn.AcceptStream(s.ctx) + if err != nil { + return + } + go func() { + err = s.handleStream(stream) + if err != nil { + stream.CancelRead(0) + stream.Close() + s.logger.Error(E.Cause(err, "handle stream request")) + } + }() + } +} + +func (s *serverSession) handleStream(stream quic.Stream) error { + buffer := buf.NewSize(2 + M.MaxSocksaddrLength) + defer buffer.Release() + _, err := buffer.ReadAtLeastFrom(stream, 2) + if err != nil { + return E.Cause(err, "read request") + } + version, _ := buffer.ReadByte() + if version != Version { + return E.New("unknown version ", buffer.Byte(0)) + } + command, _ := buffer.ReadByte() + if command != CommandConnect { + return E.New("unsupported stream command ", command) + } + destination, err := addressSerializer.ReadAddrPort(io.MultiReader(buffer, stream)) + if err != nil { + return E.Cause(err, "read request destination") + } + select { + case <-s.connDone: + return s.connErr + case <-s.authDone: + } + var conn net.Conn = &serverConn{ + Stream: stream, + destination: destination, + } + if buffer.IsEmpty() { + buffer.Release() + } else { + conn = bufio.NewCachedConn(conn, buffer) + } + ctx := s.ctx + if s.authUser.Name != "" { + ctx = auth.ContextWithUser(s.ctx, s.authUser.Name) + } + _ = s.handler.NewConnection(ctx, conn, M.Metadata{ + Source: s.source, + Destination: destination, + }) + return nil +} + +func (s *serverSession) loopHeartbeats() { + ticker := time.NewTicker(s.heartbeat) + defer ticker.Stop() + for { + select { + case <-s.connDone: + return + case <-ticker.C: + err := s.quicConn.SendMessage([]byte{Version, CommandHeartbeat}) + if err != nil { + s.closeWithError(E.Cause(err, "send heartbeat")) + } + } + } +} + +func (s *serverSession) closeWithError(err error) { + s.connAccess.Lock() + defer s.connAccess.Unlock() + select { + case <-s.connDone: + return + default: + s.connErr = err + close(s.connDone) + } + if E.IsClosedOrCanceled(err) { + s.logger.Debug(E.Cause(err, "connection failed")) + } else { + s.logger.Error(E.Cause(err, "connection failed")) + } + _ = s.quicConn.CloseWithError(0, "") +} + +type serverConn struct { + quic.Stream + destination M.Socksaddr +} + +func (c *serverConn) Read(p []byte) (n int, err error) { + n, err = c.Stream.Read(p) + return n, baderror.WrapQUIC(err) +} + +func (c *serverConn) Write(p []byte) (n int, err error) { + n, err = c.Stream.Write(p) + return n, baderror.WrapQUIC(err) +} + +func (c *serverConn) LocalAddr() net.Addr { + return c.destination +} + +func (c *serverConn) RemoteAddr() net.Addr { + return M.Socksaddr{} +} + +func (c *serverConn) Close() error { + c.Stream.CancelRead(0) + return c.Stream.Close() +} diff --git a/transport/tuic/server_packet.go b/transport/tuic/server_packet.go new file mode 100644 index 00000000..fba6118a --- /dev/null +++ b/transport/tuic/server_packet.go @@ -0,0 +1,73 @@ +package tuic + +import ( + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" +) + +func (s *serverSession) loopMessages() { + select { + case <-s.connDone: + return + case <-s.authDone: + } + for { + message, err := s.quicConn.ReceiveMessage(s.ctx) + if err != nil { + s.closeWithError(E.Cause(err, "receive message")) + return + } + hErr := s.handleMessage(message) + if hErr != nil { + s.closeWithError(E.Cause(hErr, "handle message")) + return + } + } +} + +func (s *serverSession) handleMessage(data []byte) error { + if len(data) < 2 { + return E.New("invalid message") + } + if data[0] != Version { + return E.New("unknown version ", data[0]) + } + switch data[1] { + case CommandPacket: + message := udpMessagePool.Get().(*udpMessage) + err := decodeUDPMessage(message, data[2:]) + if err != nil { + message.release() + return E.Cause(err, "decode UDP message") + } + s.handleUDPMessage(message, false) + return nil + case CommandHeartbeat: + return nil + default: + return E.New("unknown command ", data[0]) + } +} + +func (s *serverSession) handleUDPMessage(message *udpMessage, udpStream bool) { + s.udpAccess.RLock() + udpConn, loaded := s.udpConnMap[message.sessionID] + s.udpAccess.RUnlock() + if !loaded || common.Done(udpConn.ctx) { + udpConn = newUDPPacketConn(s.ctx, s.quicConn, udpStream, true, func() { + s.udpAccess.Lock() + delete(s.udpConnMap, message.sessionID) + s.udpAccess.Unlock() + }) + udpConn.sessionID = message.sessionID + s.udpAccess.Lock() + s.udpConnMap[message.sessionID] = udpConn + s.udpAccess.Unlock() + go s.handler.NewPacketConnection(udpConn.ctx, udpConn, M.Metadata{ + Source: s.source, + Destination: message.destination, + }) + } + udpConn.inputPacket(message) +} From 4b2f33ff912f370ac5f3b9b86a06b412ac73a8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 1 Aug 2023 14:20:13 +0800 Subject: [PATCH 17/20] documentation: Add TUIC --- docs/configuration/inbound/tuic.md | 82 ++++++++++++++++++ docs/configuration/inbound/tuic.zh.md | 82 ++++++++++++++++++ docs/configuration/outbound/hysteria.md | 12 +-- docs/configuration/outbound/hysteria.zh.md | 11 ++- docs/configuration/outbound/tuic.md | 86 +++++++++++++++++++ docs/configuration/outbound/tuic.zh.md | 98 ++++++++++++++++++++++ 6 files changed, 361 insertions(+), 10 deletions(-) create mode 100644 docs/configuration/inbound/tuic.md create mode 100644 docs/configuration/inbound/tuic.zh.md create mode 100644 docs/configuration/outbound/tuic.md create mode 100644 docs/configuration/outbound/tuic.zh.md diff --git a/docs/configuration/inbound/tuic.md b/docs/configuration/inbound/tuic.md new file mode 100644 index 00000000..51724e5d --- /dev/null +++ b/docs/configuration/inbound/tuic.md @@ -0,0 +1,82 @@ +### Structure + +```json +{ + "type": "tuic", + "tag": "tuic-in", + + ... // Listen Fields + + "users": [ + { + "name": "sekai", + "uuid": "059032A9-7D40-4A96-9BB1-36823D848068", + "password": "hello" + } + ], + "congestion_control": "cubic", + "auth_timeout": "3s", + "zero_rtt_handshake": false, + "heartbeat": "10s", + "tls": {} +} +``` + +!!! warning "" + + QUIC, which is required by TUIC is not included by default, see [Installation](/#installation). + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen) for details. + +### Fields + +#### users + +TUIC users + +#### users.uuid + +==Required== + +TUIC user uuid + +#### users.password + +TUIC user password + +#### congestion_control + +QUIC congestion control algorithm + +One of: `cubic`, `new_reno`, `bbr` + +`cubic` is used by default. + +#### auth_timeout + +How long the server should wait for the client to send the authentication command + +`3s` is used by default. + +#### zero_rtt_handshake + +Enable 0-RTT QUIC connection handshake on the client side +This is not impacting much on the performance, as the protocol is fully multiplexed + +!!! warning "" + Disabling this is highly recommended, as it is vulnerable to replay attacks. + See [Attack of the clones](https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/#attack-of-the-clones) + +#### heartbeat + +Interval for sending heartbeat packets for keeping the connection alive + +`10s` is used by default. + +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). \ No newline at end of file diff --git a/docs/configuration/inbound/tuic.zh.md b/docs/configuration/inbound/tuic.zh.md new file mode 100644 index 00000000..9a6f395e --- /dev/null +++ b/docs/configuration/inbound/tuic.zh.md @@ -0,0 +1,82 @@ +### 结构 + +```json +{ + "type": "tuic", + "tag": "tuic-in", + + ... // 监听字段 + + "users": [ + { + "name": "sekai", + "uuid": "059032A9-7D40-4A96-9BB1-36823D848068", + "password": "hello" + } + ], + "congestion_control": "cubic", + "auth_timeout": "3s", + "zero_rtt_handshake": false, + "heartbeat": "10s", + "tls": {} +} +``` + +!!! warning "" + + 默认安装不包含被 TUI 依赖的 QUIC,参阅 [安装](/zh/#_2)。 + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/)。 + +### 字段 + +#### users + +TUIC 用户 + +#### users.uuid + +==必填== + +TUIC 用户 UUID + +#### users.password + +TUIC 用户密码 + +#### congestion_control + +QUIC 流量控制算法 + +可选值: `cubic`, `new_reno`, `bbr` + +默认使用 `cubic`。 + +#### auth_timeout + +服务器等待客户端发送认证命令的时间 + +默认使用 `3s`。 + +#### zero_rtt_handshake + +在客户端启用 0-RTT QUIC 连接握手 +这对性能影响不大,因为协议是完全复用的 + +!!! warning "" +强烈建议禁用此功能,因为它容易受到重放攻击。 +请参阅 [Attack of the clones](https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/#attack-of-the-clones) + +#### heartbeat + +发送心跳包以保持连接存活的时间间隔 + +默认使用 `10s`。 + +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 \ No newline at end of file diff --git a/docs/configuration/outbound/hysteria.md b/docs/configuration/outbound/hysteria.md index 690177cf..8eafa99c 100644 --- a/docs/configuration/outbound/hysteria.md +++ b/docs/configuration/outbound/hysteria.md @@ -97,12 +97,6 @@ Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 (IPv4) Force enabled on for systems other than Linux and Windows (according to upstream). -#### tls - -==Required== - -TLS configuration, see [TLS](/configuration/shared/tls/#outbound). - #### network Enabled network @@ -111,6 +105,12 @@ One of `tcp` `udp`. Both is enabled by default. +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + ### Dial Fields See [Dial Fields](/configuration/shared/dial) for details. diff --git a/docs/configuration/outbound/hysteria.zh.md b/docs/configuration/outbound/hysteria.zh.md index 85184ae9..8f0eb3d9 100644 --- a/docs/configuration/outbound/hysteria.zh.md +++ b/docs/configuration/outbound/hysteria.zh.md @@ -97,10 +97,6 @@ base64 编码的认证密码。 强制为 Linux 和 Windows 以外的系统启用(根据上游)。 -==必填== - -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 - #### network 启用的网络协议。 @@ -109,6 +105,13 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 默认所有。 +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + + ### 拨号字段 参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/configuration/outbound/tuic.md b/docs/configuration/outbound/tuic.md new file mode 100644 index 00000000..d46d4cb4 --- /dev/null +++ b/docs/configuration/outbound/tuic.md @@ -0,0 +1,86 @@ +### Structure + +```json +{ + "type": "tuic", + "tag": "tuic-out", + + "server": "127.0.0.1", + "server_port": 1080, + "uuid": "2DD61D93-75D8-4DA4-AC0E-6AECE7EAC365", + "password": "hello", + "congestion_control": "cubic", + "udp_relay_mode": "native", + "zero_rtt_handshake": false, + "heartbeat": "10s", + "network": "tcp", + "tls": {}, + + ... // Dial Fields +} +``` + +!!! warning "" + + QUIC, which is required by TUIC is not included by default, see [Installation](/#installation). + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +==Required== + +The server port. + +#### uuid + +==Required== + +TUIC user uuid + +#### password + +TUIC user password + +#### congestion_control + +QUIC congestion control algorithm + +One of: `cubic`, `new_reno`, `bbr` + +`cubic` is used by default. + +#### udp_relay_mode + +UDP packet relay mode + +| Mode | Description | +|:-------|:-------------------------------------------------------------------------| +| native | native UDP characteristics | +| quic | lossless UDP relay using QUIC streams, additional overhead is introduced | + +`native` is used by default. + +#### network + +Enabled network + +One of `tcp` `udp`. + +Both is enabled by default. + +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial) for details. diff --git a/docs/configuration/outbound/tuic.zh.md b/docs/configuration/outbound/tuic.zh.md new file mode 100644 index 00000000..9d19078a --- /dev/null +++ b/docs/configuration/outbound/tuic.zh.md @@ -0,0 +1,98 @@ +### 结构 + +```json +{ + "type": "tuic", + "tag": "tuic-out", + + "server": "127.0.0.1", + "server_port": 1080, + "uuid": "2DD61D93-75D8-4DA4-AC0E-6AECE7EAC365", + "password": "hello", + "congestion_control": "cubic", + "udp_relay_mode": "native", + "zero_rtt_handshake": false, + "heartbeat": "10s", + "network": "tcp", + "tls": {}, + + ... // 拨号字段 +} +``` + +!!! warning "" + + 默认安装不包含被 TUI 依赖的 QUIC,参阅 [安装](/zh/#_2)。 + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +==必填== + +服务器端口。 + +#### uuid + +==必填== + +TUIC 用户 UUID + +#### password + +TUIC 用户密码 + +#### congestion_control + +QUIC 流量控制算法 + +可选值: `cubic`, `new_reno`, `bbr` + +默认使用 `cubic`。 + +#### udp_relay_mode + +UDP 包中继模式 + +| 模式 | 描述 | +|--------|------------------------------| +| native | 原生 UDP | +| quic | 使用 QUIC 流的无损 UDP 中继,引入了额外的开销 | + + +#### zero_rtt_handshake + +在客户端启用 0-RTT QUIC 连接握手 +这对性能影响不大,因为协议是完全复用的 + +!!! warning "" +强烈建议禁用此功能,因为它容易受到重放攻击。 +请参阅 [Attack of the clones](https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/#attack-of-the-clones) + +#### heartbeat + +发送心跳包以保持连接存活的时间间隔 + +#### network + +启用的网络协议。 + +`tcp` 或 `udp`。 + +默认所有。 + +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 From 85d3821978c1c07d1438d76a3cdf8b65c2929a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 7 Aug 2023 17:46:51 +0800 Subject: [PATCH 18/20] Pause recurring tasks when no network --- box.go | 2 ++ common/sleep/manager.go | 43 ------------------------- experimental/libbox/service.go | 14 ++++----- go.mod | 2 +- go.sum | 4 +-- outbound/urltest.go | 10 +++--- outbound/wireguard.go | 2 +- route/router.go | 50 ++++++++++++++++-------------- test/go.mod | 2 +- test/go.sum | 1 + transport/wireguard/client_bind.go | 6 ++++ 11 files changed, 52 insertions(+), 84 deletions(-) delete mode 100644 common/sleep/manager.go diff --git a/box.go b/box.go index 3ceb7a55..c82605a5 100644 --- a/box.go +++ b/box.go @@ -19,6 +19,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/service/pause" ) var _ adapter.Service = (*Box)(nil) @@ -46,6 +47,7 @@ func New(options Options) (*Box, error) { if ctx == nil { ctx = context.Background() } + ctx = pause.ContextWithDefaultManager(ctx) createdAt := time.Now() experimentalOptions := common.PtrValueOrDefault(options.Experimental) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) diff --git a/common/sleep/manager.go b/common/sleep/manager.go deleted file mode 100644 index a668e76f..00000000 --- a/common/sleep/manager.go +++ /dev/null @@ -1,43 +0,0 @@ -package sleep - -import ( - "sync" -) - -type Manager struct { - access sync.Mutex - done chan struct{} -} - -func NewManager() *Manager { - closedChan := make(chan struct{}) - close(closedChan) - return &Manager{ - done: closedChan, - } -} - -func (m *Manager) Sleep() { - m.access.Lock() - defer m.access.Unlock() - select { - case <-m.done: - default: - return - } - m.done = make(chan struct{}) -} - -func (m *Manager) Wake() { - m.access.Lock() - defer m.access.Unlock() - select { - case <-m.done: - default: - close(m.done) - } -} - -func (m *Manager) Active() <-chan struct{} { - return m.done -} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index dc8c5caf..14f0479c 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -8,7 +8,6 @@ import ( "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/common/sleep" "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" "github.com/sagernet/sing-box/experimental/libbox/platform" @@ -21,13 +20,14 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" + "github.com/sagernet/sing/service/pause" ) type BoxService struct { ctx context.Context cancel context.CancelFunc instance *box.Box - sleepManager *sleep.Manager + pauseManager pause.Manager } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { @@ -38,8 +38,8 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx, cancel := context.WithCancel(context.Background()) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage()) - sleepManager := sleep.NewManager() - ctx = service.ContextWithPtr(ctx, sleepManager) + sleepManager := pause.NewDefaultManager(ctx) + ctx = pause.ContextWithManager(ctx, sleepManager) instance, err := box.New(box.Options{ Context: ctx, Options: options, @@ -53,7 +53,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx: ctx, cancel: cancel, instance: instance, - sleepManager: sleepManager, + pauseManager: sleepManager, }, nil } @@ -67,12 +67,12 @@ func (s *BoxService) Close() error { } func (s *BoxService) Sleep() { - s.sleepManager.Sleep() + s.pauseManager.DevicePause() _ = s.instance.Router().ResetNetwork() } func (s *BoxService) Wake() { - s.sleepManager.Wake() + s.pauseManager.DeviceWake() } var _ platform.Interface = (*platformInterfaceWrapper)(nil) diff --git a/go.mod b/go.mod index 751a4f85..85dc7f72 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e - github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 + github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 go.etcd.io/bbolt v1.3.7 diff --git a/go.sum b/go.sum index 27b0c81e..3b515e2e 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfI github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM= github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs= github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY= -github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo= -github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0= +github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho= +github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= diff --git a/outbound/urltest.go b/outbound/urltest.go index bd828e4b..79ab0bf8 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -8,7 +8,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/sleep" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -20,6 +19,7 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" ) var ( @@ -153,7 +153,7 @@ type URLTestGroup struct { tolerance uint16 history *urltest.HistoryStorage checking atomic.Bool - sleepManager *sleep.Manager + pauseManager pause.Manager access sync.Mutex ticker *time.Ticker @@ -184,7 +184,7 @@ func NewURLTestGroup(ctx context.Context, router adapter.Router, logger log.Logg tolerance: tolerance, history: history, close: make(chan struct{}), - sleepManager: service.PtrFromContext[sleep.Manager](ctx), + pauseManager: pause.ManagerFromContext(ctx), } } @@ -266,9 +266,7 @@ func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound { func (g *URLTestGroup) loopCheck() { go g.CheckOutbounds(true) for { - if g.sleepManager != nil { - <-g.sleepManager.Active() - } + g.pauseManager.WaitActive() select { case <-g.close: return diff --git a/outbound/wireguard.go b/outbound/wireguard.go index eb8f65db..3627e674 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -166,7 +166,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context if err != nil { return nil, E.Cause(err, "create WireGuard device") } - wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{ + wgDevice := device.NewDevice(ctx, wireTunDevice, outbound.bind, &device.Logger{ Verbosef: func(format string, args ...interface{}) { logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) }, diff --git a/route/router.go b/route/router.go index 568db59a..ceaedb7f 100644 --- a/route/router.go +++ b/route/router.go @@ -2,6 +2,7 @@ package route import ( "context" + "errors" "net" "net/netip" "net/url" @@ -38,6 +39,7 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" + "github.com/sagernet/sing/service/pause" ) var _ adapter.Router = (*Router)(nil) @@ -78,6 +80,7 @@ type Router struct { packageManager tun.PackageManager processSearcher process.Searcher timeService adapter.TimeService + pauseManager pause.Manager clashServer adapter.ClashServer v2rayServer adapter.V2RayServer platformInterface platform.Interface @@ -109,6 +112,7 @@ func NewRouter( autoDetectInterface: options.AutoDetectInterface, defaultInterface: options.DefaultInterface, defaultMark: options.DefaultMark, + pauseManager: pause.ManagerFromContext(ctx), platformInterface: platformInterface, } router.dnsClient = dns.NewClient(dns.ClientOptions{ @@ -260,32 +264,30 @@ func NewRouter( return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute }) - if needInterfaceMonitor { - if !usePlatformDefaultInterfaceMonitor { - networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) - if err != os.ErrInvalid { - if err != nil { - return nil, err - } - router.networkMonitor = networkMonitor - networkMonitor.RegisterCallback(func() { - _ = router.interfaceFinder.update() - }) - interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ - OverrideAndroidVPN: options.OverrideAndroidVPN, - UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), - }) - if err != nil { - return nil, E.New("auto_detect_interface unsupported on current platform") - } - interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) - router.interfaceMonitor = interfaceMonitor + if !usePlatformDefaultInterfaceMonitor { + networkMonitor, err := tun.NewNetworkUpdateMonitor(router.logger) + if !((err != nil && !needInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { + if err != nil { + return nil, err + } + router.networkMonitor = networkMonitor + networkMonitor.RegisterCallback(func() { + _ = router.interfaceFinder.update() + }) + interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ + OverrideAndroidVPN: options.OverrideAndroidVPN, + UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), + }) + if err != nil { + return nil, E.New("auto_detect_interface unsupported on current platform") } - } else { - interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router.logger) interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) router.interfaceMonitor = interfaceMonitor } + } else { + interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router.logger) + interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) + router.interfaceMonitor = interfaceMonitor } needFindProcess := hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess @@ -974,8 +976,10 @@ func (r *Router) NewError(ctx context.Context, err error) { func (r *Router) notifyNetworkUpdate(event int) { if event == tun.EventNoRoute { - r.logger.Info("missing default interface") + r.pauseManager.NetworkPause() + r.logger.Error("missing default interface") } else { + r.pauseManager.NetworkWake() if C.IsAndroid && r.platformInterface == nil { var vpnStatus string if r.interfaceMonitor.AndroidVPNEnabled() { diff --git a/test/go.mod b/test/go.mod index d1aa2149..b047f84b 100644 --- a/test/go.mod +++ b/test/go.mod @@ -82,7 +82,7 @@ require ( github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect - github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 // indirect + github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect diff --git a/test/go.sum b/test/go.sum index 22e18e79..7c0cd4d5 100644 --- a/test/go.sum +++ b/test/go.sum @@ -163,6 +163,7 @@ github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+V github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY= github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo= github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0= +github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index 61f5ab7c..2b56f73a 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -5,12 +5,14 @@ import ( "net" "net/netip" "sync" + "time" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service/pause" "github.com/sagernet/wireguard-go/conn" ) @@ -27,6 +29,7 @@ type ClientBind struct { isConnect bool connectAddr M.Socksaddr reserved [3]uint8 + pauseManager pause.Manager } func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr M.Socksaddr, reserved [3]uint8) *ClientBind { @@ -38,6 +41,7 @@ func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect: isConnect, connectAddr: connectAddr, reserved: reserved, + pauseManager: pause.ManagerFromContext(ctx), } } @@ -111,6 +115,8 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) } c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server")) err = nil + time.Sleep(time.Second) + c.pauseManager.WaitActive() return } n, addr, err := udpConn.ReadFrom(packets[0]) From 9ae935e6937e15fc47f54518236fa344bffb5a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 7 Aug 2023 21:53:19 +0800 Subject: [PATCH 19/20] Save fakeip metadata immediately --- adapter/fakeip.go | 1 + experimental/clashapi/cachefile/cache.go | 13 +++++++------ experimental/clashapi/cachefile/fakeip.go | 10 ++++++++++ transport/fakeip/memory.go | 3 +++ transport/fakeip/store.go | 6 ++++++ 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/adapter/fakeip.go b/adapter/fakeip.go index 6a142245..51247c32 100644 --- a/adapter/fakeip.go +++ b/adapter/fakeip.go @@ -18,6 +18,7 @@ type FakeIPStore interface { type FakeIPStorage interface { FakeIPMetadata() *FakeIPMetadata FakeIPSaveMetadata(metadata *FakeIPMetadata) error + FakeIPSaveMetadataAsync(metadata *FakeIPMetadata) FakeIPStore(address netip.Addr, domain string) error FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) FakeIPLoad(address netip.Addr) (string, bool) diff --git a/experimental/clashapi/cachefile/cache.go b/experimental/clashapi/cachefile/cache.go index c282d715..561ae0a9 100644 --- a/experimental/clashapi/cachefile/cache.go +++ b/experimental/clashapi/cachefile/cache.go @@ -17,12 +17,13 @@ var bucketSelected = []byte("selected") var _ adapter.ClashCacheFile = (*CacheFile)(nil) type CacheFile struct { - DB *bbolt.DB - cacheID []byte - saveAccess sync.RWMutex - saveDomain map[netip.Addr]string - saveAddress4 map[string]netip.Addr - saveAddress6 map[string]netip.Addr + DB *bbolt.DB + cacheID []byte + saveAccess sync.RWMutex + saveDomain map[netip.Addr]string + saveAddress4 map[string]netip.Addr + saveAddress6 map[string]netip.Addr + saveMetadataTimer *time.Timer } func Open(path string, cacheID string) (*CacheFile, error) { diff --git a/experimental/clashapi/cachefile/fakeip.go b/experimental/clashapi/cachefile/fakeip.go index cdc279f5..a2396b59 100644 --- a/experimental/clashapi/cachefile/fakeip.go +++ b/experimental/clashapi/cachefile/fakeip.go @@ -3,6 +3,7 @@ package cachefile import ( "net/netip" "os" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common/logger" @@ -57,6 +58,15 @@ func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error { }) } +func (c *CacheFile) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) { + if timer := c.saveMetadataTimer; timer != nil { + timer.Stop() + } + c.saveMetadataTimer = time.AfterFunc(10*time.Second, func() { + _ = c.FakeIPSaveMetadata(metadata) + }) +} + func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error { return c.DB.Batch(func(tx *bbolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP) diff --git a/transport/fakeip/memory.go b/transport/fakeip/memory.go index b9e6a999..0bca4910 100644 --- a/transport/fakeip/memory.go +++ b/transport/fakeip/memory.go @@ -34,6 +34,9 @@ func (s *MemoryStorage) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) err return nil } +func (s *MemoryStorage) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) { +} + func (s *MemoryStorage) FakeIPStore(address netip.Addr, domain string) error { s.addressAccess.Lock() s.domainAccess.Lock() diff --git a/transport/fakeip/store.go b/transport/fakeip/store.go index 7571161c..96f6bf03 100644 --- a/transport/fakeip/store.go +++ b/transport/fakeip/store.go @@ -99,6 +99,12 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { address = nextAddress } s.storage.FakeIPStoreAsync(address, domain, s.logger) + s.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{ + Inet4Range: s.inet4Range, + Inet6Range: s.inet6Range, + Inet4Current: s.inet4Current, + Inet6Current: s.inet6Current, + }) return address, nil } From a0485b41cd09654ea3219ffcadd2c5409c77f61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 8 Aug 2023 16:14:03 +0800 Subject: [PATCH 20/20] Add support for multipath TCP --- common/dialer/default.go | 10 ++++++++-- common/dialer/default_go1.21.go | 11 +++++++++++ common/dialer/default_nongo1.21.go | 12 ++++++++++++ common/dialer/dialer.go | 19 +++++++++++++++---- common/tls/reality_server.go | 5 ++++- inbound/default_tcp.go | 9 ++++++++- inbound/default_tcp_go1.21.go | 11 +++++++++++ inbound/default_tcp_nongo1.21.go | 10 ++++++++++ inbound/shadowtls.go | 12 ++++++++++-- ntp/service.go | 10 +++++++--- option/inbound.go | 1 + option/outbound.go | 1 + outbound/direct.go | 6 +++++- outbound/http.go | 6 +++++- outbound/hysteria.go | 6 +++++- outbound/shadowsocks.go | 6 +++++- outbound/shadowsocksr.go | 6 +++++- outbound/shadowtls.go | 6 +++++- outbound/socks.go | 6 +++++- outbound/ssh.go | 6 +++++- outbound/tor.go | 6 +++++- outbound/trojan.go | 7 +++++-- outbound/tuic.go | 6 +++++- outbound/vless.go | 7 +++++-- outbound/vmess.go | 7 +++++-- outbound/wireguard.go | 7 +++++-- route/router.go | 9 ++++++++- transport/dhcp/server.go | 4 ++-- transport/wireguard/device_system.go | 5 +++-- 29 files changed, 181 insertions(+), 36 deletions(-) create mode 100644 common/dialer/default_go1.21.go create mode 100644 common/dialer/default_nongo1.21.go create mode 100644 inbound/default_tcp_go1.21.go create mode 100644 inbound/default_tcp_nongo1.21.go diff --git a/common/dialer/default.go b/common/dialer/default.go index f45b7809..f4148f36 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -26,7 +26,7 @@ type DefaultDialer struct { udpAddr6 string } -func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDialer { +func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) { var dialer net.Dialer var listener net.ListenConfig if options.BindInterface != "" { @@ -93,6 +93,12 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String() } + if options.TCPMultiPath { + if !multipathTCPAvailable { + return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") + } + setMultiPathTCP(&dialer4) + } return &DefaultDialer{ tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen}, tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen}, @@ -101,7 +107,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia listener, udpAddr4, udpAddr6, - } + }, nil } func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { diff --git a/common/dialer/default_go1.21.go b/common/dialer/default_go1.21.go new file mode 100644 index 00000000..360826c8 --- /dev/null +++ b/common/dialer/default_go1.21.go @@ -0,0 +1,11 @@ +//go:build go1.21 + +package dialer + +import "net" + +const multipathTCPAvailable = true + +func setMultiPathTCP(dialer *net.Dialer) { + dialer.SetMultipathTCP(true) +} diff --git a/common/dialer/default_nongo1.21.go b/common/dialer/default_nongo1.21.go new file mode 100644 index 00000000..6e564673 --- /dev/null +++ b/common/dialer/default_nongo1.21.go @@ -0,0 +1,12 @@ +//go:build !go1.21 + +package dialer + +import ( + "net" +) + +const multipathTCPAvailable = false + +func setMultiPathTCP(dialer *net.Dialer) { +} diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index 5b1750cc..0f5c913a 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -6,13 +6,24 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" N "github.com/sagernet/sing/common/network" ) -func New(router adapter.Router, options option.DialerOptions) N.Dialer { - var dialer N.Dialer +func MustNew(router adapter.Router, options option.DialerOptions) N.Dialer { + return common.Must1(New(router, options)) +} + +func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) { + var ( + dialer N.Dialer + err error + ) if options.Detour == "" { - dialer = NewDefault(router, options) + dialer, err = NewDefault(router, options) + if err != nil { + return nil, err + } } else { dialer = NewDetour(router, options.Detour) } @@ -20,5 +31,5 @@ func New(router adapter.Router, options option.DialerOptions) N.Dialer { if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" { dialer = NewResolveDialer(router, dialer, domainStrategy, time.Duration(options.FallbackDelay)) } - return dialer + return dialer, nil } diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 0cd339c9..fd1a6815 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -101,7 +101,10 @@ func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Log tlsConfig.ShortIds[shortID] = true } - handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions) + handshakeDialer, err := dialer.New(router, options.Reality.Handshake.DialerOptions) + if err != nil { + return nil, err + } tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) } diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go index 238762dc..8de81ba4 100644 --- a/inbound/default_tcp.go +++ b/inbound/default_tcp.go @@ -18,7 +18,14 @@ func (a *myInboundAdapter) ListenTCP() (net.Listener, error) { bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) var tcpListener net.Listener if !a.listenOptions.TCPFastOpen { - tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr()) + var listenConfig net.ListenConfig + if a.listenOptions.TCPMultiPath { + if !multipathTCPAvailable { + return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") + } + setMultiPathTCP(&listenConfig) + } + tcpListener, err = listenConfig.Listen(a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) } else { tcpListener, err = tfo.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr()) } diff --git a/inbound/default_tcp_go1.21.go b/inbound/default_tcp_go1.21.go new file mode 100644 index 00000000..1352e056 --- /dev/null +++ b/inbound/default_tcp_go1.21.go @@ -0,0 +1,11 @@ +//go:build go1.21 + +package inbound + +import "net" + +const multipathTCPAvailable = true + +func setMultiPathTCP(listenConfig *net.ListenConfig) { + listenConfig.SetMultipathTCP(true) +} diff --git a/inbound/default_tcp_nongo1.21.go b/inbound/default_tcp_nongo1.21.go new file mode 100644 index 00000000..62dc5b9f --- /dev/null +++ b/inbound/default_tcp_nongo1.21.go @@ -0,0 +1,10 @@ +//go:build !go1.21 + +package inbound + +import "net" + +const multipathTCPAvailable = false + +func setMultiPathTCP(listenConfig *net.ListenConfig) { +} diff --git a/inbound/shadowtls.go b/inbound/shadowtls.go index cd7a4db8..59b7c107 100644 --- a/inbound/shadowtls.go +++ b/inbound/shadowtls.go @@ -40,12 +40,20 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context if options.Version > 1 { handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) for serverName, serverOptions := range options.HandshakeForServerName { + handshakeDialer, err := dialer.New(router, serverOptions.DialerOptions) + if err != nil { + return nil, err + } handshakeForServerName[serverName] = shadowtls.HandshakeConfig{ Server: serverOptions.ServerOptions.Build(), - Dialer: dialer.New(router, serverOptions.DialerOptions), + Dialer: handshakeDialer, } } } + handshakeDialer, err := dialer.New(router, options.Handshake.DialerOptions) + if err != nil { + return nil, err + } service, err := shadowtls.NewService(shadowtls.ServiceConfig{ Version: options.Version, Password: options.Password, @@ -54,7 +62,7 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context }), Handshake: shadowtls.HandshakeConfig{ Server: options.Handshake.ServerOptions.Build(), - Dialer: dialer.New(router, options.Handshake.DialerOptions), + Dialer: handshakeDialer, }, HandshakeForServerName: handshakeForServerName, StrictMode: options.StrictMode, diff --git a/ntp/service.go b/ntp/service.go index a8dc6ea2..3ca1de3c 100644 --- a/ntp/service.go +++ b/ntp/service.go @@ -31,7 +31,7 @@ type Service struct { clockOffset time.Duration } -func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service { +func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) (*Service, error) { ctx, cancel := common.ContextWithCancelCause(ctx) server := options.ServerOptions.Build() if server.Port == 0 { @@ -43,15 +43,19 @@ func NewService(ctx context.Context, router adapter.Router, logger logger.Logger } else { interval = 30 * time.Minute } + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } return &Service{ ctx: ctx, cancel: cancel, server: server, writeToSystem: options.WriteToSystem, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, logger: logger, ticker: time.NewTicker(interval), - } + }, nil } func (s *Service) Start() error { diff --git a/option/inbound.go b/option/inbound.go index b09b3a65..64b45e6c 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -125,6 +125,7 @@ type ListenOptions struct { Listen *ListenAddress `json:"listen,omitempty"` ListenPort uint16 `json:"listen_port,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` UDPTimeout int64 `json:"udp_timeout,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index ab7aa0eb..5e837741 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -134,6 +134,7 @@ type DialerOptions struct { ReuseAddr bool `json:"reuse_addr,omitempty"` ConnectTimeout Duration `json:"connect_timeout,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` diff --git a/outbound/direct.go b/outbound/direct.go index 5a0cd34d..ed126830 100644 --- a/outbound/direct.go +++ b/outbound/direct.go @@ -38,6 +38,10 @@ type Direct struct { func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { options.UDPFragmentDefault = true + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &Direct{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeDirect, @@ -49,7 +53,7 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti }, domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, proxyProto: options.ProxyProtocol, } if options.ProxyProtocol > 2 { diff --git a/outbound/http.go b/outbound/http.go index e07f2f01..2e265da1 100644 --- a/outbound/http.go +++ b/outbound/http.go @@ -26,7 +26,11 @@ type HTTP struct { } func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) { - detour, err := tls.NewDialerFromOptions(router, dialer.New(router, options.DialerOptions), options.Server, common.PtrValueOrDefault(options.TLS)) + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } + detour, err := tls.NewDialerFromOptions(router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/outbound/hysteria.go b/outbound/hysteria.go index d055c8f9..9ed5b6d8 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -117,6 +117,10 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if down < hysteria.MinSpeedBPS { return nil, E.New("invalid down speed") } + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } return &Hysteria{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeHysteria, @@ -127,7 +131,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL dependencies: withDialerDependency(options.DialerOptions), }, ctx: ctx, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), tlsConfig: tlsConfig, quicConfig: quicConfig, diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index 2f7d92cb..c8a9b0a8 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -39,6 +39,10 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte if err != nil { return nil, err } + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &Shadowsocks{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeShadowsocks, @@ -48,7 +52,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte tag: tag, dependencies: withDialerDependency(options.DialerOptions), }, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, method: method, serverAddr: options.ServerOptions.Build(), } diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go index 6b30595d..0787cbd2 100644 --- a/outbound/shadowsocksr.go +++ b/outbound/shadowsocksr.go @@ -37,6 +37,10 @@ type ShadowsocksR struct { } func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) { + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &ShadowsocksR{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeShadowsocksR, @@ -46,7 +50,7 @@ func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.Cont tag: tag, dependencies: withDialerDependency(options.DialerOptions), }, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } var cipher string diff --git a/outbound/shadowtls.go b/outbound/shadowtls.go index 7eeb1bae..9f02124d 100644 --- a/outbound/shadowtls.go +++ b/outbound/shadowtls.go @@ -72,11 +72,15 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig) } } + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } client, err := shadowtls.NewClient(shadowtls.ClientConfig{ Version: options.Version, Password: options.Password, Server: options.ServerOptions.Build(), - Dialer: dialer.New(router, options.DialerOptions), + Dialer: outboundDialer, TLSHandshake: tlsHandshakeFunc, Logger: logger, }) diff --git a/outbound/socks.go b/outbound/socks.go index 97579fd3..48107480 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -37,6 +37,10 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio if err != nil { return nil, err } + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &Socks{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeSOCKS, @@ -46,7 +50,7 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio tag: tag, dependencies: withDialerDependency(options.DialerOptions), }, - client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password), + client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), resolve: version == socks.Version4, } uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions) diff --git a/outbound/ssh.go b/outbound/ssh.go index 93e5a7bc..0c6a9894 100644 --- a/outbound/ssh.go +++ b/outbound/ssh.go @@ -44,6 +44,10 @@ type SSH struct { } func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) { + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &SSH{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeSSH, @@ -54,7 +58,7 @@ func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger dependencies: withDialerDependency(options.DialerOptions), }, ctx: ctx, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), user: options.User, hostKeyAlgorithms: options.HostKeyAlgorithms, diff --git a/outbound/tor.go b/outbound/tor.go index 0e81066e..76c7955d 100644 --- a/outbound/tor.go +++ b/outbound/tor.go @@ -66,6 +66,10 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger } startConf.TorrcFile = torrcFile } + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } return &Tor{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeTor, @@ -76,7 +80,7 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger dependencies: withDialerDependency(options.DialerOptions), }, ctx: ctx, - proxy: NewProxyListener(ctx, logger, dialer.New(router, options.DialerOptions)), + proxy: NewProxyListener(ctx, logger, outboundDialer), startConf: &startConf, options: options.Options, }, nil diff --git a/outbound/trojan.go b/outbound/trojan.go index a12041e4..db11d105 100644 --- a/outbound/trojan.go +++ b/outbound/trojan.go @@ -33,6 +33,10 @@ type Trojan struct { } func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &Trojan{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeTrojan, @@ -42,11 +46,10 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog tag: tag, dependencies: withDialerDependency(options.DialerOptions), }, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), key: trojan.Key(options.Password), } - var err error if options.TLS != nil { outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { diff --git a/outbound/tuic.go b/outbound/tuic.go index 42585ab3..3b0ff157 100644 --- a/outbound/tuic.go +++ b/outbound/tuic.go @@ -57,9 +57,13 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge case "quic": udpStream = true } + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } client, err := tuic.NewClient(tuic.ClientOptions{ Context: ctx, - Dialer: dialer.New(router, options.DialerOptions), + Dialer: outboundDialer, ServerAddress: options.ServerOptions.Build(), TLSConfig: tlsConfig, UUID: userUUID, diff --git a/outbound/vless.go b/outbound/vless.go index 12574467..4a130403 100644 --- a/outbound/vless.go +++ b/outbound/vless.go @@ -36,6 +36,10 @@ type VLESS struct { } func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) { + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &VLESS{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeVLESS, @@ -45,10 +49,9 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg tag: tag, dependencies: withDialerDependency(options.DialerOptions), }, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } - var err error if options.TLS != nil { outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { diff --git a/outbound/vmess.go b/outbound/vmess.go index 6f7735fc..b07f13d7 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -35,6 +35,10 @@ type VMess struct { } func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } outbound := &VMess{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeVMess, @@ -44,10 +48,9 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg tag: tag, dependencies: withDialerDependency(options.DialerOptions), }, - dialer: dialer.New(router, options.DialerOptions), + dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } - var err error if options.TLS != nil { outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { diff --git a/outbound/wireguard.go b/outbound/wireguard.go index 3627e674..bc24e2e7 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -65,7 +65,11 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context connectAddr = options.ServerOptions.Build() } } - outbound.bind = wireguard.NewClientBind(ctx, outbound, dialer.New(router, options.DialerOptions), isConnect, connectAddr, reserved) + outboundDialer, err := dialer.New(router, options.DialerOptions) + if err != nil { + return nil, err + } + outbound.bind = wireguard.NewClientBind(ctx, outbound, outboundDialer, isConnect, connectAddr, reserved) localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build) if len(localPrefixes) == 0 { return nil, E.New("missing local address") @@ -157,7 +161,6 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context mtu = 1408 } var wireTunDevice wireguard.Device - var err error if !options.SystemInterface && tun.WithGVisor { wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu) } else { diff --git a/route/router.go b/route/router.go index ceaedb7f..bf668dd8 100644 --- a/route/router.go +++ b/route/router.go @@ -38,7 +38,9 @@ import ( F "github.com/sagernet/sing/common/format" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + serviceNTP "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/uot" + "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) @@ -319,7 +321,12 @@ func NewRouter( } } if ntpOptions.Enabled { - router.timeService = ntp.NewService(ctx, router, logFactory.NewLogger("ntp"), ntpOptions) + timeService, err := ntp.NewService(ctx, router, logFactory.NewLogger("ntp"), ntpOptions) + if err != nil { + return nil, err + } + service.ContextWith[serviceNTP.TimeService](ctx, timeService) + router.timeService = timeService } return router, nil } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index e3f1aed8..35e8783f 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -248,10 +248,10 @@ func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Ad }), ","), "]") } - serverDialer := dialer.NewDefault(t.router, option.DialerOptions{ + serverDialer := common.Must1(dialer.NewDefault(t.router, option.DialerOptions{ BindInterface: iface.Name, UDPFragmentDefault: true, - }) + })) var transports []dns.Transport for _, serverAddr := range serverAddrs { serverTransport, err := dns.NewUDPTransport(t.name, t.ctx, serverDialer, M.Socksaddr{Addr: serverAddr, Port: 53}) diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index f2325a9c..98626404 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" wgTun "github.com/sagernet/wireguard-go/tun" @@ -58,9 +59,9 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes inet6Address = inet6Addresses[0].Addr() } return &SystemDevice{ - dialer: dialer.NewDefault(router, option.DialerOptions{ + dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{ BindInterface: interfaceName, - }), + })), device: tunInterface, name: interfaceName, mtu: int(mtu),