From 737162e75aba5619ab811c959cdba13ee8bd0057 Mon Sep 17 00:00:00 2001 From: Zephyruso <176294927+Zephyruso@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:26:35 +0800 Subject: [PATCH 001/185] Add `/dns/flush-clash` meta api --- experimental/clashapi/cache.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/experimental/clashapi/cache.go b/experimental/clashapi/cache.go index 9c088a82..4df1f890 100644 --- a/experimental/clashapi/cache.go +++ b/experimental/clashapi/cache.go @@ -14,6 +14,7 @@ import ( func cacheRouter(ctx context.Context) http.Handler { r := chi.NewRouter() r.Post("/fakeip/flush", flushFakeip(ctx)) + r.Post("/dns/flush", flushDNS(ctx)) return r } @@ -31,3 +32,13 @@ func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Reques render.NoContent(w, r) } } + +func flushDNS(ctx context.Context) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + if dnsRouter != nil { + dnsRouter.ClearCache() + } + render.NoContent(w, r) + } +} From 1f030805407349cc2e3671834fffb0ded50702fc Mon Sep 17 00:00:00 2001 From: neletor <209430099+neletor@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:18:34 +0800 Subject: [PATCH 002/185] Add support for ech retry configs --- common/tls/client.go | 26 ++++++++++++++++++-------- dns/transport/tls.go | 11 +++-------- protocol/anytls/outbound.go | 16 ++++------------ protocol/http/outbound.go | 2 +- protocol/trojan/outbound.go | 7 ++++--- protocol/vless/outbound.go | 12 ++++++------ protocol/vmess/outbound.go | 12 ++++++------ transport/v2raygrpclite/client.go | 10 ++-------- transport/v2rayhttp/client.go | 7 ++----- transport/v2rayhttpupgrade/client.go | 9 +-------- transport/v2raywebsocket/client.go | 9 +-------- 11 files changed, 48 insertions(+), 73 deletions(-) diff --git a/common/tls/client.go b/common/tls/client.go index afdb5a42..7b4149a3 100644 --- a/common/tls/client.go +++ b/common/tls/client.go @@ -2,10 +2,11 @@ package tls import ( "context" + "crypto/tls" + "errors" "net" "os" - "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/badtls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" @@ -14,7 +15,7 @@ import ( aTLS "github.com/sagernet/sing/common/tls" ) -func NewDialerFromOptions(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { +func NewDialerFromOptions(ctx context.Context, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { if !options.Enabled { return dialer, nil } @@ -79,20 +80,29 @@ func (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd } func (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) { - return d.dialContext(ctx, destination) + return d.dialContext(ctx, destination, true) } -func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr) (Conn, error) { +func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr, echRetry bool) (Conn, error) { conn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination) if err != nil { return nil, err } tlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config) - if err != nil { - conn.Close() - return nil, err + if err == nil { + return tlsConn, nil } - return tlsConn, nil + conn.Close() + if echRetry { + var echErr *tls.ECHRejectionError + if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 { + if echConfig, isECH := d.config.(ECHCapableConfig); isECH { + echConfig.SetECHConfigList(echErr.RetryConfigList) + } + } + return d.dialContext(ctx, destination, false) + } + return nil, err } func (d *defaultDialer) Upstream() any { diff --git a/dns/transport/tls.go b/dns/transport/tls.go index afa988cc..9cb35fd9 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -30,7 +30,7 @@ func RegisterTLS(registry *dns.TransportRegistry) { type TLSTransport struct { dns.TransportAdapter logger logger.ContextLogger - dialer N.Dialer + dialer tls.Dialer serverAddr M.Socksaddr tlsConfig tls.Config access sync.Mutex @@ -67,7 +67,7 @@ func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer return &TLSTransport{ TransportAdapter: adapter, logger: logger, - dialer: dialer, + dialer: tls.NewDialer(dialer, tlsConfig), serverAddr: serverAddr, tlsConfig: tlsConfig, } @@ -100,15 +100,10 @@ func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M return response, nil } } - tcpConn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr) + tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr) if err != nil { return nil, err } - tlsConn, err := tls.ClientHandshake(ctx, tcpConn, t.tlsConfig) - if err != nil { - tcpConn.Close() - return nil, err - } return t.exchange(message, &tlsDNSConn{Conn: tlsConn}) } diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go index c8d8bf43..6b9e047c 100644 --- a/protocol/anytls/outbound.go +++ b/protocol/anytls/outbound.go @@ -27,7 +27,7 @@ func RegisterOutbound(registry *outbound.Registry) { type Outbound struct { outbound.Adapter - dialer N.Dialer + dialer tls.Dialer server M.Socksaddr tlsConfig tls.Config client *anytls.Client @@ -66,7 +66,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outbound.dialer = outboundDialer + + outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig) client, err := anytls.NewClient(ctx, anytls.ClientConfig{ Password: options.Password, @@ -99,16 +100,7 @@ func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) } func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) { - conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.server) - if err != nil { - return nil, err - } - tlsConn, err := tls.ClientHandshake(ctx, conn, h.tlsConfig) - if err != nil { - common.Close(tlsConn, conn) - return nil, err - } - return tlsConn, nil + return h.dialer.DialTLSContext(ctx, h.server) } func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go index 0570dde5..3b631b39 100644 --- a/protocol/http/outbound.go +++ b/protocol/http/outbound.go @@ -34,7 +34,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - detour, err := tls.NewDialerFromOptions(ctx, router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS)) + detour, err := tls.NewDialerFromOptions(ctx, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index cd290386..dc2e0fe4 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -34,6 +34,7 @@ type Outbound struct { key [56]byte multiplexDialer *mux.Client tlsConfig tls.Config + tlsDialer tls.Dialer transport adapter.V2RayClientTransport } @@ -54,6 +55,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } + outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig) } if options.Transport != nil { outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) @@ -121,11 +123,10 @@ func (h *trojanDialer) DialContext(ctx context.Context, network string, destinat var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { common.Close(conn) diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index b95a36f7..a96d12a0 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -35,6 +35,7 @@ type Outbound struct { serverAddr M.Socksaddr multiplexDialer *mux.Client tlsConfig tls.Config + tlsDialer tls.Dialer transport adapter.V2RayClientTransport packetAddr bool xudp bool @@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } + outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig) } if options.Transport != nil { outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) @@ -140,11 +142,10 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { return nil, err @@ -183,11 +184,10 @@ func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { common.Close(conn) diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index bf76ab3d..716570f3 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -35,6 +35,7 @@ type Outbound struct { serverAddr M.Socksaddr multiplexDialer *mux.Client tlsConfig tls.Config + tlsDialer tls.Dialer transport adapter.V2RayClientTransport packetAddr bool xudp bool @@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } + outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig) } if options.Transport != nil { outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) @@ -154,11 +156,10 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { common.Close(conn) @@ -182,11 +183,10 @@ func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) + } else if h.tlsDialer != nil { + conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) - if err == nil && h.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig) - } } if err != nil { return nil, err diff --git a/transport/v2raygrpclite/client.go b/transport/v2raygrpclite/client.go index de8915a1..b2aab911 100644 --- a/transport/v2raygrpclite/client.go +++ b/transport/v2raygrpclite/client.go @@ -29,7 +29,6 @@ var defaultClientHeader = http.Header{ type Client struct { ctx context.Context - dialer N.Dialer serverAddr M.Socksaddr transport *http2.Transport options option.V2RayGRPCOptions @@ -46,7 +45,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } client := &Client{ ctx: ctx, - dialer: dialer, serverAddr: serverAddr, options: options, transport: &http2.Transport{ @@ -62,7 +60,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt }, host: host, } - if tlsConfig == nil { client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) { return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) @@ -71,12 +68,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) } + tlsDialer := tls.NewDialer(dialer, tlsConfig) client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) { - conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - if err != nil { - return nil, err - } - return tls.ClientHandshake(ctx, conn, tlsConfig) + return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr)) } } diff --git a/transport/v2rayhttp/client.go b/transport/v2rayhttp/client.go index a105a4f3..6c327cd6 100644 --- a/transport/v2rayhttp/client.go +++ b/transport/v2rayhttp/client.go @@ -47,15 +47,12 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) } + tlsDialer := tls.NewDialer(dialer, tlsConfig) transport = &http2.Transport{ ReadIdleTimeout: time.Duration(options.IdleTimeout), PingTimeout: time.Duration(options.PingTimeout), DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) { - conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) - if err != nil { - return nil, err - } - return tls.ClientHandshake(ctx, conn, tlsConfig) + return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr)) }, } } diff --git a/transport/v2rayhttpupgrade/client.go b/transport/v2rayhttpupgrade/client.go index e2b86b1f..f282d3f6 100644 --- a/transport/v2rayhttpupgrade/client.go +++ b/transport/v2rayhttpupgrade/client.go @@ -23,7 +23,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil) type Client struct { dialer N.Dialer - tlsConfig tls.Config serverAddr M.Socksaddr requestURL url.URL headers http.Header @@ -35,6 +34,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{"http/1.1"}) } + dialer = tls.NewDialer(dialer, tlsConfig) } var host string if options.Host != "" { @@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } return &Client{ dialer: dialer, - tlsConfig: tlsConfig, serverAddr: serverAddr, requestURL: requestURL, headers: headers, @@ -78,12 +77,6 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { if err != nil { return nil, err } - if c.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig) - if err != nil { - return nil, err - } - } request := &http.Request{ Method: http.MethodGet, URL: &c.requestURL, diff --git a/transport/v2raywebsocket/client.go b/transport/v2raywebsocket/client.go index 748bae4c..e5630109 100644 --- a/transport/v2raywebsocket/client.go +++ b/transport/v2raywebsocket/client.go @@ -26,7 +26,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil) type Client struct { dialer N.Dialer - tlsConfig tls.Config serverAddr M.Socksaddr requestURL url.URL headers http.Header @@ -39,6 +38,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{"http/1.1"}) } + dialer = tls.NewDialer(dialer, tlsConfig) } var requestURL url.URL if tlsConfig == nil { @@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } return &Client{ dialer, - tlsConfig, serverAddr, requestURL, headers, @@ -79,12 +78,6 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h if err != nil { return nil, err } - if c.tlsConfig != nil { - conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig) - if err != nil { - return nil, err - } - } var deadlineConn net.Conn if deadline.NeedAdditionalReadDeadline(conn) { deadlineConn = deadline.NewConn(conn) From fecdbf20de67c59b34ede2ec1ab89f219accf714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 7 Oct 2025 13:19:57 +0800 Subject: [PATCH 003/185] Fix ECH retry support --- common/tls/client.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/common/tls/client.go b/common/tls/client.go index 7b4149a3..e372d183 100644 --- a/common/tls/client.go +++ b/common/tls/client.go @@ -89,20 +89,18 @@ func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr return nil, err } tlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config) - if err == nil { - return tlsConn, nil - } - conn.Close() - if echRetry { + if err != nil { + conn.Close() var echErr *tls.ECHRejectionError - if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 { + if echRetry && errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 { if echConfig, isECH := d.config.(ECHCapableConfig); isECH { echConfig.SetECHConfigList(echErr.RetryConfigList) + return d.dialContext(ctx, destination, false) } } - return d.dialContext(ctx, destination, false) + return nil, err } - return nil, err + return tlsConn, nil } func (d *defaultDialer) Upstream() any { From 65264afdf9d220512cc2214ea40e6da6ad1e5483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 14 Aug 2025 10:55:18 +0800 Subject: [PATCH 004/185] Add interface address rule items --- cmd/sing-box/cmd_rule_set_compile.go | 19 +++- common/srs/binary.go | 101 ++++++++++++++++++- common/srs/ip_cidr.go | 33 ++++++ constant/rule.go | 3 +- option/rule.go | 75 +++++++------- option/rule_dns.go | 81 ++++++++------- option/rule_set.go | 49 ++++----- route/rule/rule_default.go | 15 +++ route/rule/rule_default_interface_address.go | 56 ++++++++++ route/rule/rule_dns.go | 15 +++ route/rule/rule_headless.go | 12 ++- route/rule/rule_interface_address.go | 62 ++++++++++++ route/rule/rule_network_interface_address.go | 64 ++++++++++++ route/rule/rule_set.go | 4 +- route/rule/rule_set_local.go | 6 +- route/rule/rule_set_remote.go | 6 +- 16 files changed, 490 insertions(+), 111 deletions(-) create mode 100644 common/srs/ip_cidr.go create mode 100644 route/rule/rule_default_interface_address.go create mode 100644 route/rule/rule_interface_address.go create mode 100644 route/rule/rule_network_interface_address.go diff --git a/cmd/sing-box/cmd_rule_set_compile.go b/cmd/sing-box/cmd_rule_set_compile.go index 0c44a2a1..73655b12 100644 --- a/cmd/sing-box/cmd_rule_set_compile.go +++ b/cmd/sing-box/cmd_rule_set_compile.go @@ -6,8 +6,10 @@ import ( "strings" "github.com/sagernet/sing-box/common/srs" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing/common/json" "github.com/spf13/cobra" @@ -69,7 +71,7 @@ func compileRuleSet(sourcePath string) error { if err != nil { return err } - err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version) + err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options)) if err != nil { outputFile.Close() os.Remove(outputPath) @@ -78,3 +80,18 @@ func compileRuleSet(sourcePath string) error { outputFile.Close() return nil } + +func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 { + if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool { + return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 || + len(rule.DefaultInterfaceAddress) > 0 + }) { + version = C.RuleSetVersion3 + } + if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool { + return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained + }) { + version = C.RuleSetVersion2 + } + return version +} diff --git a/common/srs/binary.go b/common/srs/binary.go index d7cda6eb..96b578f5 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -12,6 +12,8 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/domain" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" "github.com/sagernet/sing/common/varbin" "go4.org/netipx" @@ -41,6 +43,8 @@ const ( ruleItemNetworkType ruleItemNetworkIsExpensive ruleItemNetworkIsConstrained + ruleItemNetworkInterfaceAddress + ruleItemDefaultInterfaceAddress ruleItemFinal uint8 = 0xFF ) @@ -230,6 +234,51 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea rule.NetworkIsExpensive = true case ruleItemNetworkIsConstrained: rule.NetworkIsConstrained = true + case ruleItemNetworkInterfaceAddress: + rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) + var size uint64 + size, err = binary.ReadUvarint(reader) + if err != nil { + return + } + for i := uint64(0); i < size; i++ { + var key uint8 + err = binary.Read(reader, binary.BigEndian, &key) + if err != nil { + return + } + var value []badoption.Prefixable + var prefixCount uint64 + prefixCount, err = binary.ReadUvarint(reader) + if err != nil { + return + } + for j := uint64(0); j < prefixCount; j++ { + var prefix netip.Prefix + prefix, err = readPrefix(reader) + if err != nil { + return + } + value = append(value, badoption.Prefixable(prefix)) + } + rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value) + } + case ruleItemDefaultInterfaceAddress: + var value []badoption.Prefixable + var prefixCount uint64 + prefixCount, err = binary.ReadUvarint(reader) + if err != nil { + return + } + for j := uint64(0); j < prefixCount; j++ { + var prefix netip.Prefix + prefix, err = readPrefix(reader) + if err != nil { + return + } + value = append(value, badoption.Prefixable(prefix)) + } + rule.DefaultInterfaceAddress = value case ruleItemFinal: err = binary.Read(reader, binary.BigEndian, &rule.Invert) return @@ -346,7 +395,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen } if len(rule.NetworkType) > 0 { if generateVersion < C.RuleSetVersion3 { - return E.New("network_type rule item is only supported in version 3 or later") + return E.New("`network_type` rule item is only supported in version 3 or later") } err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType) if err != nil { @@ -354,17 +403,67 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen } } if rule.NetworkIsExpensive { + if generateVersion < C.RuleSetVersion3 { + return E.New("`network_is_expensive` rule item is only supported in version 3 or later") + } err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive) if err != nil { return err } } if rule.NetworkIsConstrained { + if generateVersion < C.RuleSetVersion3 { + return E.New("`network_is_constrained` rule item is only supported in version 3 or later") + } err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained) if err != nil { return err } } + if rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 { + if generateVersion < C.RuleSetVersion4 { + return E.New("`network_interface_address` rule item is only supported in version 4 or later") + } + err = writer.WriteByte(ruleItemNetworkInterfaceAddress) + if err != nil { + return err + } + _, err = varbin.WriteUvarint(writer, uint64(rule.NetworkInterfaceAddress.Size())) + if err != nil { + return err + } + for _, entry := range rule.NetworkInterfaceAddress.Entries() { + err = binary.Write(writer, binary.BigEndian, uint8(entry.Key.Build())) + if err != nil { + return err + } + for _, rawPrefix := range entry.Value { + err = writePrefix(writer, rawPrefix.Build(netip.Prefix{})) + if err != nil { + return err + } + } + } + } + if len(rule.DefaultInterfaceAddress) > 0 { + if generateVersion < C.RuleSetVersion4 { + return E.New("`default_interface_address` rule item is only supported in version 4 or later") + } + err = writer.WriteByte(ruleItemDefaultInterfaceAddress) + if err != nil { + return err + } + _, err = varbin.WriteUvarint(writer, uint64(len(rule.DefaultInterfaceAddress))) + if err != nil { + return err + } + for _, rawPrefix := range rule.DefaultInterfaceAddress { + err = writePrefix(writer, rawPrefix.Build(netip.Prefix{})) + if err != nil { + return err + } + } + } if len(rule.WIFISSID) > 0 { err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID) if err != nil { diff --git a/common/srs/ip_cidr.go b/common/srs/ip_cidr.go new file mode 100644 index 00000000..93ae84ad --- /dev/null +++ b/common/srs/ip_cidr.go @@ -0,0 +1,33 @@ +package srs + +import ( + "encoding/binary" + "net/netip" + + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/varbin" +) + +func readPrefix(reader varbin.Reader) (netip.Prefix, error) { + addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian) + if err != nil { + return netip.Prefix{}, err + } + prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian) + if err != nil { + return netip.Prefix{}, err + } + return netip.PrefixFrom(M.AddrFromIP(addrSlice), int(prefixBits)), nil +} + +func writePrefix(writer varbin.Writer, prefix netip.Prefix) error { + err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice()) + if err != nil { + return err + } + err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits())) + if err != nil { + return err + } + return nil +} diff --git a/constant/rule.go b/constant/rule.go index 336c3b38..71441450 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -22,7 +22,8 @@ const ( RuleSetVersion1 = 1 + iota RuleSetVersion2 RuleSetVersion3 - RuleSetVersionCurrent = RuleSetVersion3 + RuleSetVersion4 + RuleSetVersionCurrent = RuleSetVersion4 ) const ( diff --git a/option/rule.go b/option/rule.go index 41bcc126..d12b679d 100644 --- a/option/rule.go +++ b/option/rule.go @@ -67,42 +67,45 @@ func (r Rule) IsValid() bool { } type RawDefaultRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Client badoption.Listable[string] `json:"client,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Client badoption.Listable[string] `json:"client,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 87b15017..bbab993c 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -68,45 +68,48 @@ func (r DNSRule) IsValid() bool { } type RawDefaultDNSRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - IPAcceptAny bool `json:"ip_accept_any,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - Outbound badoption.Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + IPAcceptAny bool `json:"ip_accept_any,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + Outbound badoption.Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index 610d7ba2..2775d743 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -182,28 +182,31 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` + + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` @@ -240,7 +243,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4: v = r.Options default: return nil, E.New("unknown rule-set version: ", r.Version) diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 08e21e5f..e0677b97 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -246,6 +246,21 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 { + item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 { + item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DefaultInterfaceAddress) > 0 { + item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.RuleSet) > 0 { var matchSource bool if options.RuleSetIPCIDRMatchSource { diff --git a/route/rule/rule_default_interface_address.go b/route/rule/rule_default_interface_address.go new file mode 100644 index 00000000..940d3a06 --- /dev/null +++ b/route/rule/rule_default_interface_address.go @@ -0,0 +1,56 @@ +package rule + +import ( + "net/netip" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" +) + +var _ RuleItem = (*DefaultInterfaceAddressItem)(nil) + +type DefaultInterfaceAddressItem struct { + interfaceMonitor tun.DefaultInterfaceMonitor + interfaceAddresses []netip.Prefix +} + +func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[badoption.Prefixable]) *DefaultInterfaceAddressItem { + item := &DefaultInterfaceAddressItem{ + interfaceMonitor: networkManager.InterfaceMonitor(), + interfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)), + } + for _, prefixable := range interfaceAddresses { + item.interfaceAddresses = append(item.interfaceAddresses, prefixable.Build(netip.Prefix{})) + } + return item +} + +func (r *DefaultInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { + defaultInterface := r.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return false + } + for _, address := range r.interfaceAddresses { + if common.All(defaultInterface.Addresses, func(it netip.Prefix) bool { + return !address.Overlaps(it) + }) { + return false + } + } + return true +} + +func (r *DefaultInterfaceAddressItem) String() string { + addressLen := len(r.interfaceAddresses) + switch { + case addressLen == 1: + return "default_interface_address=" + r.interfaceAddresses[0].String() + case addressLen > 3: + return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses[:3], netip.Prefix.String), " ") + "...]" + default: + return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses, netip.Prefix.String), " ") + "]" + } +} diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 30442abf..d9570cae 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -247,6 +247,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 { + item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 { + item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DefaultInterfaceAddress) > 0 { + item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.RuleSet) > 0 { var matchSource bool if options.RuleSetIPCIDRMatchSource { diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index ba17ca37..689e6e3e 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -164,13 +164,21 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR item := NewWIFISSIDItem(networkManager, options.WIFISSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) - } if len(options.WIFIBSSID) > 0 { item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) - + } + if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 { + item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } + if len(options.DefaultInterfaceAddress) > 0 { + item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) } } if len(options.AdGuardDomain) > 0 { diff --git a/route/rule/rule_interface_address.go b/route/rule/rule_interface_address.go new file mode 100644 index 00000000..53ece683 --- /dev/null +++ b/route/rule/rule_interface_address.go @@ -0,0 +1,62 @@ +package rule + +import ( + "net/netip" + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +var _ RuleItem = (*InterfaceAddressItem)(nil) + +type InterfaceAddressItem struct { + networkManager adapter.NetworkManager + interfaceAddresses map[string][]netip.Prefix + description string +} + +func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]]) *InterfaceAddressItem { + item := &InterfaceAddressItem{ + networkManager: networkManager, + interfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()), + } + var entryDescriptions []string + for _, entry := range interfaceAddresses.Entries() { + prefixes := make([]netip.Prefix, 0, len(entry.Value)) + for _, prefixable := range entry.Value { + prefixes = append(prefixes, prefixable.Build(netip.Prefix{})) + } + item.interfaceAddresses[entry.Key] = prefixes + entryDescriptions = append(entryDescriptions, entry.Key+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ",")) + } + item.description = "interface_address=[" + strings.Join(entryDescriptions, " ") + "]" + return item +} + +func (r *InterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { + interfaces := r.networkManager.InterfaceFinder().Interfaces() + for ifName, addresses := range r.interfaceAddresses { + iface := common.Find(interfaces, func(it control.Interface) bool { + return it.Name == ifName + }) + if iface.Name == "" { + return false + } + if common.All(addresses, func(address netip.Prefix) bool { + return common.All(iface.Addresses, func(it netip.Prefix) bool { + return !address.Overlaps(it) + }) + }) { + return false + } + } + return true +} + +func (r *InterfaceAddressItem) String() string { + return r.description +} diff --git a/route/rule/rule_network_interface_address.go b/route/rule/rule_network_interface_address.go new file mode 100644 index 00000000..c365be3b --- /dev/null +++ b/route/rule/rule_network_interface_address.go @@ -0,0 +1,64 @@ +package rule + +import ( + "net/netip" + "strings" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/common/json/badoption" +) + +var _ RuleItem = (*NetworkInterfaceAddressItem)(nil) + +type NetworkInterfaceAddressItem struct { + networkManager adapter.NetworkManager + interfaceAddresses map[C.InterfaceType][]netip.Prefix + description string +} + +func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) *NetworkInterfaceAddressItem { + item := &NetworkInterfaceAddressItem{ + networkManager: networkManager, + interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()), + } + var entryDescriptions []string + for _, entry := range interfaceAddresses.Entries() { + prefixes := make([]netip.Prefix, 0, len(entry.Value)) + for _, prefixable := range entry.Value { + prefixes = append(prefixes, prefixable.Build(netip.Prefix{})) + } + item.interfaceAddresses[entry.Key.Build()] = prefixes + entryDescriptions = append(entryDescriptions, entry.Key.Build().String()+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ",")) + } + item.description = "network_interface_address=[" + strings.Join(entryDescriptions, " ") + "]" + return item +} + +func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool { + interfaces := r.networkManager.NetworkInterfaces() +match: + for ifType, addresses := range r.interfaceAddresses { + for _, networkInterface := range interfaces { + if networkInterface.Type != ifType { + continue + } + if common.Any(networkInterface.Addresses, func(it netip.Prefix) bool { + return common.Any(addresses, func(prefix netip.Prefix) bool { + return prefix.Overlaps(it) + }) + }) { + continue match + } + } + return false + } + return true +} + +func (r *NetworkInterfaceAddressItem) String() string { + return r.description +} diff --git a/route/rule/rule_set.go b/route/rule/rule_set.go index 5e639a47..39068dbf 100644 --- a/route/rule/rule_set.go +++ b/route/rule/rule_set.go @@ -42,7 +42,7 @@ func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet { } } -func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { +func HasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool { for _, rule := range rules { switch rule.Type { case C.RuleTypeDefault: @@ -50,7 +50,7 @@ func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultH return true } case C.RuleTypeLogical: - if hasHeadlessRule(rule.LogicalOptions.Rules, cond) { + if HasHeadlessRule(rule.LogicalOptions.Rules, cond) { return true } } diff --git a/route/rule/rule_set_local.go b/route/rule/rule_set_local.go index bda7c5ab..b09915ed 100644 --- a/route/rule/rule_set_local.go +++ b/route/rule/rule_set_local.go @@ -138,9 +138,9 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error { } } var metadata adapter.RuleSetMetadata - metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule) - metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule) - metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule) + metadata.ContainsProcessRule = HasHeadlessRule(headlessRules, isProcessHeadlessRule) + metadata.ContainsWIFIRule = HasHeadlessRule(headlessRules, isWIFIHeadlessRule) + metadata.ContainsIPCIDRRule = HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule) s.access.Lock() s.rules = rules s.metadata = metadata diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 73e46f4f..3aba76ba 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -190,9 +190,9 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error { } } s.access.Lock() - s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) - s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) - s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) + s.metadata.ContainsProcessRule = HasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule) + s.metadata.ContainsWIFIRule = HasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule) + s.metadata.ContainsIPCIDRRule = HasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule) s.rules = rules callbacks := s.callbacks.Array() s.access.Unlock() From 5be1887f92a967d8b4feed332c7d8b4b3bd1e35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 14 Aug 2025 11:04:04 +0800 Subject: [PATCH 005/185] documentation: Add interface address rule items --- docs/configuration/dns/rule.md | 49 ++++++++++++++++++ docs/configuration/dns/rule.zh.md | 49 ++++++++++++++++++ docs/configuration/route/rule.md | 49 ++++++++++++++++++ docs/configuration/route/rule.zh.md | 51 ++++++++++++++++++- docs/configuration/rule-set/headless-rule.md | 33 ++++++++++++ .../rule-set/headless-rule.zh.md | 33 ++++++++++++ docs/configuration/rule-set/source-format.md | 5 ++ .../rule-set/source-format.zh.md | 5 ++ 8 files changed, 273 insertions(+), 1 deletion(-) diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 5d5cb7c3..524b70a2 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -2,6 +2,12 @@ icon: material/alert-decagram --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [ip_accept_any](#ip_accept_any) @@ -130,6 +136,19 @@ icon: material/alert-decagram ], "network_is_expensive": false, "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], @@ -359,6 +378,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms). Match if network is in Low Data Mode. +#### interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match interface address. + +#### network_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Matches network interface (same values as `network_type`) address. + +#### default_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match default interface address. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index 8973eba2..bc812c77 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -2,6 +2,12 @@ icon: material/alert-decagram --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + !!! quote "sing-box 1.12.0 中的更改" :material-plus: [ip_accept_any](#ip_accept_any) @@ -130,6 +136,19 @@ icon: material/alert-decagram ], "network_is_expensive": false, "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], @@ -358,6 +377,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. 匹配如果网络在低数据模式下。 +#### interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配接口地址。 + +#### network_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络接口(可用值同 `network_type`)地址。 + +#### default_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配默认接口地址。 + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 43954a78..a4864e44 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -2,6 +2,12 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + !!! quote "Changes in sing-box 1.11.0" :material-plus: [action](#action) @@ -128,6 +134,19 @@ icon: material/new-box ], "network_is_expensive": false, "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], @@ -363,6 +382,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms). Match if network is in Low Data Mode. +#### interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match interface address. + +#### network_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Matches network interface (same values as `network_type`) address. + +#### default_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match default interface address. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 8deab2f3..d30dcad9 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -2,6 +2,12 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [interface_address](#interface_address) + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + !!! quote "sing-box 1.11.0 中的更改" :material-plus: [action](#action) @@ -125,6 +131,19 @@ icon: material/new-box ], "network_is_expensive": false, "network_is_constrained": false, + "interface_address": { + "en0": [ + "2000::/3" + ] + }, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], @@ -337,7 +356,7 @@ icon: material/new-box 匹配网络类型。 -Available values: `wifi`, `cellular`, `ethernet` and `other`. +可用值: `wifi`, `cellular`, `ethernet` and `other`. #### network_is_expensive @@ -360,6 +379,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. 匹配如果网络在低数据模式下。 +#### interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配接口地址。 + +#### network_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络接口(可用值同 `network_type`)地址。 + +#### default_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配默认接口地址。 + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/rule-set/headless-rule.md b/docs/configuration/rule-set/headless-rule.md index bdad22f0..89cccd39 100644 --- a/docs/configuration/rule-set/headless-rule.md +++ b/docs/configuration/rule-set/headless-rule.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + !!! quote "Changes in sing-box 1.11.0" :material-plus: [network_type](#network_type) @@ -78,6 +83,14 @@ icon: material/new-box ], "network_is_expensive": false, "network_is_constrained": false, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], @@ -225,6 +238,26 @@ such as Cellular or a Personal Hotspot (on Apple platforms). Match if network is in Low Data Mode. +#### network_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms. + +Matches network interface (same values as `network_type`) address. + +#### default_interface_address + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux, Windows, and macOS. + +Match default interface address. + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/rule-set/headless-rule.zh.md b/docs/configuration/rule-set/headless-rule.zh.md index c5281504..d539d710 100644 --- a/docs/configuration/rule-set/headless-rule.zh.md +++ b/docs/configuration/rule-set/headless-rule.zh.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [network_interface_address](#network_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + !!! quote "sing-box 1.11.0 中的更改" :material-plus: [network_type](#network_type) @@ -78,6 +83,14 @@ icon: material/new-box ], "network_is_expensive": false, "network_is_constrained": false, + "network_interface_address": { + "wifi": [ + "2000::/3" + ] + }, + "default_interface_address": [ + "2000::/3" + ], "wifi_ssid": [ "My WIFI" ], @@ -221,6 +234,26 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. 匹配如果网络在低数据模式下。 +#### network_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅在 Android 与 Apple 平台图形客户端中支持。 + +匹配网络接口(可用值同 `network_type`)地址。 + +#### default_interface_address + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux、Windows 和 macOS. + +匹配默认接口地址。 + #### wifi_ssid !!! quote "" diff --git a/docs/configuration/rule-set/source-format.md b/docs/configuration/rule-set/source-format.md index 1dcc1d44..47d620b1 100644 --- a/docs/configuration/rule-set/source-format.md +++ b/docs/configuration/rule-set/source-format.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: version `4` + !!! quote "Changes in sing-box 1.11.0" :material-plus: version `3` @@ -36,6 +40,7 @@ Version of rule-set. * 1: sing-box 1.8.0: Initial rule-set version. * 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets. * 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items. +* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items. #### rules diff --git a/docs/configuration/rule-set/source-format.zh.md b/docs/configuration/rule-set/source-format.zh.md index 3dacaea7..30c0679f 100644 --- a/docs/configuration/rule-set/source-format.zh.md +++ b/docs/configuration/rule-set/source-format.zh.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: version `4` + !!! quote "sing-box 1.11.0 中的更改" :material-plus: version `3` @@ -36,6 +40,7 @@ icon: material/new-box * 1: sing-box 1.8.0: 初始规则集版本。 * 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。 * 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。 +* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。 #### rules From 239e6ec701ff9f3e63c7f5f61587d07c58414318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Aug 2025 12:45:06 +0800 Subject: [PATCH 006/185] Add `preferred_by` route rule item --- adapter/outbound.go | 6 ++ option/rule.go | 1 + protocol/tailscale/dns_transport.go | 15 +---- protocol/tailscale/endpoint.go | 67 ++++++++++++++++++++-- protocol/wireguard/endpoint.go | 10 ++++ protocol/wireguard/outbound.go | 10 ++++ route/rule/rule_default.go | 7 ++- route/rule/rule_item_preferred_by.go | 86 ++++++++++++++++++++++++++++ transport/wireguard/endpoint.go | 11 ++++ 9 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 route/rule/rule_item_preferred_by.go diff --git a/adapter/outbound.go b/adapter/outbound.go index 2c2b1091..517aa0fe 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -2,6 +2,7 @@ package adapter import ( "context" + "net/netip" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -18,6 +19,11 @@ type Outbound interface { N.Dialer } +type OutboundWithPreferredRoutes interface { + PreferredDomain(domain string) bool + PreferredAddress(address netip.Addr) bool +} + type OutboundRegistry interface { option.OutboundOptionsRegistry CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) diff --git a/option/rule.go b/option/rule.go index d12b679d..44927e3a 100644 --- a/option/rule.go +++ b/option/rule.go @@ -103,6 +103,7 @@ type RawDefaultRule struct { InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"` NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` + PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` Invert bool `json:"invert,omitempty"` diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go index 3447b6b2..51115717 100644 --- a/protocol/tailscale/dns_transport.go +++ b/protocol/tailscale/dns_transport.go @@ -7,7 +7,6 @@ import ( "net/netip" "net/url" "os" - "reflect" "strings" "sync" @@ -47,8 +46,6 @@ type DNSTransport struct { acceptDefaultResolvers bool dnsRouter adapter.DNSRouter endpointManager adapter.EndpointManager - cfg *wgcfg.Config - dnsCfg *nDNS.Config endpoint *Endpoint routePrefixes []netip.Prefix routes map[string][]adapter.DNSTransport @@ -83,10 +80,10 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error { if !isTailscale { return E.New("endpoint is not Tailscale: ", t.endpointTag) } - if ep.onReconfig != nil { + if ep.onReconfigHook != nil { return E.New("only one Tailscale DNS server is allowed for single endpoint") } - ep.onReconfig = t.onReconfig + ep.onReconfigHook = t.onReconfig t.endpoint = ep return nil } @@ -95,14 +92,6 @@ func (t *DNSTransport) Reset() { } func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) { - if cfg == nil || dnsCfg == nil { - return - } - if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) { - return - } - t.cfg = cfg - t.dnsCfg = dnsCfg err := t.updateDNSServers(routerCfg, dnsCfg) if err != nil { t.logger.Error(E.Cause(err, "update DNS servers")) diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index c2558836..1e67c392 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -10,6 +10,7 @@ import ( "net/url" "os" "path/filepath" + "reflect" "runtime" "strings" "sync/atomic" @@ -50,8 +51,14 @@ import ( "github.com/sagernet/tailscale/version" "github.com/sagernet/tailscale/wgengine" "github.com/sagernet/tailscale/wgengine/filter" + "github.com/sagernet/tailscale/wgengine/router" + "github.com/sagernet/tailscale/wgengine/wgcfg" + + "go4.org/netipx" ) +var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + func init() { version.SetVersion("sing-box " + C.Version) } @@ -71,7 +78,12 @@ type Endpoint struct { server *tsnet.Server stack *stack.Stack filter *atomic.Pointer[filter.Filter] - onReconfig wgengine.ReconfigListener + onReconfigHook wgengine.ReconfigListener + + cfg *wgcfg.Config + dnsCfg *tsDNS.Config + routeDomains common.TypedValue[map[string]bool] + routePrefixes atomic.Pointer[netipx.IPSet] acceptRoutes bool exitNode string @@ -218,9 +230,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { if err != nil { return err } - if t.onReconfig != nil { - t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig) - } + t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig) ipStack := t.server.ExportNetstack().ExportIPStack() gErr := ipStack.SetSpoofing(tun.DefaultNIC, true) @@ -256,7 +266,6 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { return E.Cause(err, "update prefs") } t.filter = localBackend.ExportFilter() - go t.watchState() return nil } @@ -491,10 +500,58 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } +func (t *Endpoint) PreferredDomain(domain string) bool { + routeDomains := t.routeDomains.Load() + if routeDomains == nil { + return false + } + return routeDomains[strings.ToLower(domain)] +} + +func (t *Endpoint) PreferredAddress(address netip.Addr) bool { + routePrefixes := t.routePrefixes.Load() + if routePrefixes == nil { + return false + } + return routePrefixes.Contains(address) +} + func (t *Endpoint) Server() *tsnet.Server { return t.server } +func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *tsDNS.Config) { + if cfg == nil || dnsCfg == nil { + return + } + if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) { + return + } + t.cfg = cfg + t.dnsCfg = dnsCfg + + routeDomains := make(map[string]bool) + for fqdn := range dnsCfg.Routes { + routeDomains[fqdn.WithoutTrailingDot()] = true + } + for _, fqdn := range dnsCfg.SearchDomains { + routeDomains[fqdn.WithoutTrailingDot()] = true + } + t.routeDomains.Store(routeDomains) + + var builder netipx.IPSetBuilder + for _, peer := range cfg.Peers { + for _, allowedIP := range peer.AllowedIPs { + builder.AddPrefix(allowedIP) + } + } + t.routePrefixes.Store(common.Must1(builder.IPSet())) + + if t.onReconfigHook != nil { + t.onReconfigHook(cfg, routerCfg, dnsCfg) + } +} + func addressFromAddr(destination netip.Addr) tcpip.Address { if destination.Is6() { return tcpip.AddrFrom16(destination.As16()) diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 4165d126..207670c2 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -22,6 +22,8 @@ import ( "github.com/sagernet/sing/service" ) +var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + func RegisterEndpoint(registry *endpoint.Registry) { endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint) } @@ -210,3 +212,11 @@ func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n } return w.endpoint.ListenPacket(ctx, destination) } + +func (w *Endpoint) PreferredDomain(domain string) bool { + return false +} + +func (w *Endpoint) PreferredAddress(address netip.Addr) bool { + return w.endpoint.Lookup(address) != nil +} diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 129e69b8..fa58d959 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -21,6 +21,8 @@ import ( "github.com/sagernet/sing/service" ) +var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil) + func RegisterOutbound(registry *outbound.Registry) { outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) } @@ -158,3 +160,11 @@ func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n } return o.endpoint.ListenPacket(ctx, destination) } + +func (o *Outbound) PreferredDomain(domain string) bool { + return false +} + +func (o *Outbound) PreferredAddress(address netip.Addr) bool { + return o.endpoint.Lookup(address) != nil +} diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index e0677b97..66a6e5a7 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -117,7 +117,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio if len(options.DomainRegex) > 0 { item, err := NewDomainRegexItem(options.DomainRegex) if err != nil { - return nil, E.Cause(err, "domain_regex") + return nil, err } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) @@ -261,6 +261,11 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } + if len(options.PreferredBy) > 0 { + item := NewPreferredByItem(ctx, options.PreferredBy) + rule.items = append(rule.items, item) + rule.allItems = append(rule.allItems, item) + } if len(options.RuleSet) > 0 { var matchSource bool if options.RuleSetIPCIDRMatchSource { diff --git a/route/rule/rule_item_preferred_by.go b/route/rule/rule_item_preferred_by.go new file mode 100644 index 00000000..42c8a627 --- /dev/null +++ b/route/rule/rule_item_preferred_by.go @@ -0,0 +1,86 @@ +package rule + +import ( + "context" + "strings" + + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/service" +) + +var _ RuleItem = (*PreferredByItem)(nil) + +type PreferredByItem struct { + ctx context.Context + outboundTags []string + outbounds []adapter.OutboundWithPreferredRoutes +} + +func NewPreferredByItem(ctx context.Context, outboundTags []string) *PreferredByItem { + return &PreferredByItem{ + ctx: ctx, + outboundTags: outboundTags, + } +} + +func (r *PreferredByItem) Start() error { + outboundManager := service.FromContext[adapter.OutboundManager](r.ctx) + for _, outboundTag := range r.outboundTags { + rawOutbound, loaded := outboundManager.Outbound(outboundTag) + if !loaded { + return E.New("outbound not found: ", outboundTag) + } + outboundWithPreferredRoutes, withRoutes := rawOutbound.(adapter.OutboundWithPreferredRoutes) + if !withRoutes { + return E.New("outbound type does not support preferred routes: ", rawOutbound.Type()) + } + r.outbounds = append(r.outbounds, outboundWithPreferredRoutes) + } + return nil +} + +func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool { + var domainHost string + if metadata.Domain != "" { + domainHost = metadata.Domain + } else { + domainHost = metadata.Destination.Fqdn + } + if domainHost != "" { + for _, outbound := range r.outbounds { + if outbound.PreferredDomain(domainHost) { + return true + } + } + } + if metadata.Destination.IsIP() { + for _, outbound := range r.outbounds { + if outbound.PreferredAddress(metadata.Destination.Addr) { + return true + } + } + } + if len(metadata.DestinationAddresses) > 0 { + for _, address := range metadata.DestinationAddresses { + for _, outbound := range r.outbounds { + if outbound.PreferredAddress(address) { + return true + } + } + } + } + return false +} + +func (r *PreferredByItem) String() string { + description := "preferred_by=" + pLen := len(r.outboundTags) + if pLen == 1 { + description += F.ToString(r.outboundTags[0]) + } else { + description += "[" + strings.Join(F.MapToString(r.outboundTags), " ") + "]" + } + return description +} diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 3801640f..2adf7832 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -8,7 +8,9 @@ import ( "net" "net/netip" "os" + "reflect" "strings" + "unsafe" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -30,6 +32,7 @@ type Endpoint struct { allowedAddress []netip.Prefix tunDevice Device device *device.Device + allowedIPs *device.AllowedIPs pause pause.Manager pauseCallback *list.Element[pause.Callback] } @@ -191,6 +194,7 @@ func (e *Endpoint) Start(resolve bool) error { if e.pause != nil { e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated) } + e.allowedIPs = (*device.AllowedIPs)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(wgDevice)).FieldByName("allowedips").UnsafeAddr())) return nil } @@ -218,6 +222,13 @@ func (e *Endpoint) Close() error { return nil } +func (e *Endpoint) Lookup(address netip.Addr) *device.Peer { + if e.allowedIPs == nil { + return nil + } + return e.allowedIPs.Lookup(address.AsSlice()) +} + func (e *Endpoint) onPauseUpdated(event int) { switch event { case pause.EventDevicePaused, pause.EventNetworkPause: From 87eaf3ce6efe33d722372c94ac285bf510c81112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Aug 2025 12:55:03 +0800 Subject: [PATCH 007/185] documentation: Add `preferred_by` route rule item --- docs/configuration/route/rule.md | 18 +++++++++++++++++- docs/configuration/route/rule.zh.md | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index a4864e44..89eecaaf 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [interface_address](#interface_address) :material-plus: [network_interface_address](#network_interface_address) - :material-plus: [default_interface_address](#default_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + :material-plus: [preferred_by](#preferred_by) !!! quote "Changes in sing-box 1.11.0" @@ -153,6 +154,10 @@ icon: material/new-box "wifi_bssid": [ "00:00:00:00:00:00" ], + "preferred_by": [ + "tailscale", + "wireguard" + ], "rule_set": [ "geoip-cn", "geosite-cn" @@ -428,6 +433,17 @@ Match WiFi SSID. Match WiFi BSSID. +#### preferred_by + +!!! question "Since sing-box 1.13.0" + +Match specified outbounds' preferred routes. + +| Type | Match | +|-------------|-----------------------------------------------| +| `tailscale` | Match MagicDNS domains and peers' allowed IPs | +| `wireguard` | Match peers's allowed IPs | + #### rule_set !!! question "Since sing-box 1.8.0" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index d30dcad9..100344da 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [interface_address](#interface_address) :material-plus: [network_interface_address](#network_interface_address) - :material-plus: [default_interface_address](#default_interface_address) + :material-plus: [default_interface_address](#default_interface_address) + :material-plus: [preferred_by](#preferred_by) !!! quote "sing-box 1.11.0 中的更改" @@ -150,6 +151,10 @@ icon: material/new-box "wifi_bssid": [ "00:00:00:00:00:00" ], + "preferred_by": [ + "tailscale", + "wireguard" + ], "rule_set": [ "geoip-cn", "geosite-cn" @@ -425,6 +430,17 @@ icon: material/new-box 匹配 WiFi BSSID。 +#### preferred_by + +!!! question "自 sing-box 1.13.0 起" + +匹配制定出站的首选路由。 + +| 类型 | 匹配 | +|-------------|--------------------------------| +| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs | +| `wireguard` | 匹配对端的 allowed IPs | + #### rule_set !!! question "自 sing-box 1.8.0 起" From 0bd98a300f6d4c20551f10e3609e445d0e263dfc Mon Sep 17 00:00:00 2001 From: xchacha20-poly1305 Date: Fri, 15 Aug 2025 15:00:28 +0800 Subject: [PATCH 008/185] Fix rule set version --- option/rule_set.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/option/rule_set.go b/option/rule_set.go index 2775d743..fa839e35 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -258,7 +258,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { } var v any switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4: v = &r.Options case 0: return E.New("missing rule-set version") @@ -275,7 +275,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) { switch r.Version { - case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3: + case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4: default: return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version)) } From 1c846df903c9f03d595941f01a853e51d05255bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Aug 2025 23:31:01 +0800 Subject: [PATCH 009/185] Use resolved in local DNS server if available --- dns/transport/local/local.go | 31 +++- dns/transport/local/local_fallback.go | 14 +- dns/transport/local/local_resolved.go | 14 ++ dns/transport/local/local_resolved_linux.go | 150 ++++++++++++++++++++ dns/transport/local/local_resolved_stub.go | 14 ++ 5 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 dns/transport/local/local_resolved.go create mode 100644 dns/transport/local/local_resolved_linux.go create mode 100644 dns/transport/local/local_resolved_stub.go diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 51badec4..3118b6c5 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -16,6 +16,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/buf" 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" @@ -26,9 +27,11 @@ var _ adapter.DNSTransport = (*Transport)(nil) type Transport struct { dns.TransportAdapter - ctx context.Context - hosts *hosts.File - dialer N.Dialer + ctx context.Context + logger logger.ContextLogger + hosts *hosts.File + dialer N.Dialer + resolved ResolvedResolver } func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { @@ -39,20 +42,42 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt return &Transport{ TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), ctx: ctx, + logger: logger, hosts: hosts.NewFile(hosts.DefaultPath), dialer: transportDialer, }, nil } func (t *Transport) Start(stage adapter.StartStage) error { + switch stage { + case adapter.StartStateInitialize: + resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) + if err == nil { + err = resolvedResolver.Start() + if err == nil { + t.resolved = resolvedResolver + } else { + t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + } + } + } return nil } func (t *Transport) Close() error { + if t.resolved != nil { + return t.resolved.Close() + } return nil } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if t.resolved != nil { + resolverObject := t.resolved.Object() + if resolverObject != nil { + return t.resolved.Exchange(resolverObject, ctx, message) + } + } question := message.Question[0] if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name)) diff --git a/dns/transport/local/local_fallback.go b/dns/transport/local/local_fallback.go index 1e7e0238..fcc01cc9 100644 --- a/dns/transport/local/local_fallback.go +++ b/dns/transport/local/local_fallback.go @@ -33,6 +33,10 @@ func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag str if err != nil { return nil, err } + platformInterface := service.FromContext[platform.Interface](ctx) + if platformInterface == nil { + return transport, nil + } return &FallbackTransport{ DNSTransport: transport, ctx: ctx, @@ -40,11 +44,11 @@ func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag str } func (f *FallbackTransport) Start(stage adapter.StartStage) error { - if stage != adapter.StartStateStart { - return nil + err := f.DNSTransport.Start(stage) + if err != nil { + return err } - platformInterface := service.FromContext[platform.Interface](f.ctx) - if platformInterface == nil { + if stage != adapter.StartStatePostStart { return nil } inboundManager := service.FromContext[adapter.InboundManager](f.ctx) @@ -59,7 +63,7 @@ func (f *FallbackTransport) Start(stage adapter.StartStage) error { } func (f *FallbackTransport) Close() error { - return nil + return f.DNSTransport.Close() } func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { diff --git a/dns/transport/local/local_resolved.go b/dns/transport/local/local_resolved.go new file mode 100644 index 00000000..2a1a190f --- /dev/null +++ b/dns/transport/local/local_resolved.go @@ -0,0 +1,14 @@ +package local + +import ( + "context" + + mDNS "github.com/miekg/dns" +) + +type ResolvedResolver interface { + Start() error + Close() error + Object() any + Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) +} diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go new file mode 100644 index 00000000..1d841a29 --- /dev/null +++ b/dns/transport/local/local_resolved_linux.go @@ -0,0 +1,150 @@ +package local + +import ( + "context" + "os" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/service/resolved" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/service" + + "github.com/godbus/dbus/v5" + mDNS "github.com/miekg/dns" +) + +type DBusResolvedResolver struct { + logger logger.ContextLogger + interfaceMonitor tun.DefaultInterfaceMonitor + systemBus *dbus.Conn + resoledObject common.TypedValue[dbus.BusObject] + closeOnce sync.Once +} + +func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { + interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor() + if interfaceMonitor == nil { + return nil, os.ErrInvalid + } + systemBus, err := dbus.SystemBus() + if err != nil { + return nil, err + } + return &DBusResolvedResolver{ + logger: logger, + interfaceMonitor: interfaceMonitor, + systemBus: systemBus, + }, nil +} + +func (t *DBusResolvedResolver) Start() error { + t.updateStatus() + err := t.systemBus.BusObject().AddMatchSignal( + "org.freedesktop.DBus", + "NameOwnerChanged", + dbus.WithMatchSender("org.freedesktop.DBus"), + dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"), + ).Err + if err != nil { + return E.Cause(err, "configure resolved restart listener") + } + go t.loopUpdateStatus() + return nil +} + +func (t *DBusResolvedResolver) Close() error { + t.closeOnce.Do(func() { + if t.systemBus != nil { + _ = t.systemBus.Close() + } + }) + return nil +} + +func (t *DBusResolvedResolver) Object() any { + return t.resoledObject.Load() +} + +func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + defaultInterface := t.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + question := message.Question[0] + call := object.(*dbus.Object).CallWithContext( + ctx, + "org.freedesktop.resolve1.Manager.ResolveRecord", + 0, + int32(defaultInterface.Index), + question.Name, + question.Qclass, + question.Qtype, + uint64(0), + ) + if call.Err != nil { + return nil, E.Cause(call.Err, " resolve record via resolved") + } + var ( + records []resolved.ResourceRecord + outflags uint64 + ) + err := call.Store(&records, &outflags) + if err != nil { + return nil, err + } + response := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: message.Id, + Response: true, + Authoritative: true, + RecursionDesired: true, + RecursionAvailable: true, + Rcode: mDNS.RcodeSuccess, + }, + Question: []mDNS.Question{question}, + } + for _, record := range records { + var rr mDNS.RR + rr, _, err = mDNS.UnpackRR(record.Data, 0) + if err != nil { + return nil, E.Cause(err, "unpack resource record") + } + response.Answer = append(response.Answer, rr) + } + return response, nil +} + +func (t *DBusResolvedResolver) loopUpdateStatus() { + signalChan := make(chan *dbus.Signal, 1) + t.systemBus.Signal(signalChan) + for signal := range signalChan { + var restarted bool + if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" { + if len(signal.Body) != 3 || signal.Body[2].(string) == "" { + continue + } else { + restarted = true + } + } + if restarted { + t.updateStatus() + } + } +} + +func (t *DBusResolvedResolver) updateStatus() { + dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") + err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err + if err != nil { + if t.resoledObject.Swap(nil) != nil { + t.logger.Debug("systemd-resolved service is gone") + } + return + } + t.resoledObject.Store(dbusObject) + t.logger.Debug("using systemd-resolved service as resolver") +} diff --git a/dns/transport/local/local_resolved_stub.go b/dns/transport/local/local_resolved_stub.go new file mode 100644 index 00000000..ac23c4f3 --- /dev/null +++ b/dns/transport/local/local_resolved_stub.go @@ -0,0 +1,14 @@ +//go:build !linux + +package local + +import ( + "context" + "os" + + "github.com/sagernet/sing/common/logger" +) + +func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { + return nil, os.ErrInvalid +} From 48f84b31d679bff95787992cda760d8824fb28b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Aug 2025 14:19:49 +0800 Subject: [PATCH 010/185] Improve `local` DNS server on darwin We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does. This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable. It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously. In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`. --- .github/workflows/build.yml | 19 +- Makefile | 2 +- box.go | 7 +- cmd/internal/build_libbox/main.go | 18 +- dns/transport/local/local.go | 200 ++------------------ dns/transport/local/local_darwin.go | 134 +++++++++++++ dns/transport/local/local_darwin_dhcp.go | 16 ++ dns/transport/local/local_darwin_nodhcp.go | 15 ++ dns/transport/local/local_fallback.go | 208 --------------------- dns/transport/local/local_shared.go | 191 +++++++++++++++++++ dns/transport/local/resolv_darwin_cgo.go | 55 ------ dns/transport/local/resolv_unix.go | 2 +- dns/transport_manager.go | 29 ++- option/dns.go | 13 +- 14 files changed, 433 insertions(+), 476 deletions(-) create mode 100644 dns/transport/local/local_darwin.go create mode 100644 dns/transport/local/local_darwin_dhcp.go create mode 100644 dns/transport/local/local_darwin_nodhcp.go delete mode 100644 dns/transport/local/local_fallback.go create mode 100644 dns/transport/local/local_shared.go delete mode 100644 dns/transport/local/resolv_darwin_cgo.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ef5ae20..0e4da8c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,7 +149,7 @@ jobs: TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build - if: matrix.os != 'android' + if: matrix.os != 'darwin' && matrix.os != 'android' run: | set -xeuo pipefail mkdir -p dist @@ -165,6 +165,23 @@ jobs: GOMIPS: ${{ matrix.gomips }} GOMIPS64: ${{ matrix.gomips }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build darwin + if: matrix.os == 'darwin' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build Android if: matrix.os == 'android' run: | diff --git a/Makefile b/Makefile index a98faeac..b92bc6b6 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid=" +PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0" MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)" MAIN = ./cmd/sing-box PREFIX ?= $(shell go env GOPATH) diff --git a/box.go b/box.go index 8a38f6ae..d43a3c95 100644 --- a/box.go +++ b/box.go @@ -323,13 +323,14 @@ func New(options Options) (*Box, error) { option.DirectOutboundOptions{}, ) }) - dnsTransportManager.Initialize(common.Must1( - local.NewTransport( + dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) { + return local.NewTransport( ctx, logFactory.NewLogger("dns/local"), "local", option.LocalDNSServerOptions{}, - ))) + ) + }) if platformInterface != nil { err = platformInterface.Initialize(networkManager) if err != nil { diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index c7bdf6cf..71df1ae4 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -59,8 +59,8 @@ func init() { if err != nil { currentTag = "unknown" } - sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") - debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) + sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") + debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+"-s -w -buildid= -checklinkname=0") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") darwinTags = append(darwinTags, "with_dhcp") @@ -106,19 +106,17 @@ func buildAndroid() { "-libname=box", } - if !debugEnabled { - sharedFlags[3] = sharedFlags[3] + " -checklinkname=0" - args = append(args, sharedFlags...) - } else { - debugFlags[1] = debugFlags[1] + " -checklinkname=0" - args = append(args, debugFlags...) - } - tags := append(sharedTags, memcTags...) if debugEnabled { tags = append(tags, debugTags...) } + if !debugEnabled { + args = append(args, sharedFlags...) + } else { + args = append(args, debugFlags...) + } + args = append(args, "-tags", strings.Join(tags, ",")) args = append(args, "./experimental/libbox") diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 3118b6c5..f1d67d16 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -1,28 +1,27 @@ +//go:build !darwin + package local import ( "context" - "errors" - "math/rand" - "syscall" - "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/dns/transport" "github.com/sagernet/sing-box/dns/transport/hosts" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/buf" 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" mDNS "github.com/miekg/dns" ) +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport) +} + var _ adapter.DNSTransport = (*Transport)(nil) type Transport struct { @@ -31,6 +30,7 @@ type Transport struct { logger logger.ContextLogger hosts *hosts.File dialer N.Dialer + preferGo bool resolved ResolvedResolver } @@ -45,19 +45,22 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt logger: logger, hosts: hosts.NewFile(hosts.DefaultPath), dialer: transportDialer, + preferGo: options.PreferGo, }, nil } func (t *Transport) Start(stage adapter.StartStage) error { switch stage { case adapter.StartStateInitialize: - resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) - if err == nil { - err = resolvedResolver.Start() + if !t.preferGo { + resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) if err == nil { - t.resolved = resolvedResolver - } else { - t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + err = resolvedResolver.Start() + if err == nil { + t.resolved = resolvedResolver + } else { + t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + } } } } @@ -85,174 +88,5 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil } } - systemConfig := getSystemDNSConfig(t.ctx) - if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) { - return t.exchangeSingleRequest(ctx, systemConfig, message, question.Name) - } else { - return t.exchangeParallel(ctx, systemConfig, message, question.Name) - } -} - -func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { - var lastErr error - for _, fqdn := range systemConfig.nameList(domain) { - response, err := t.tryOneName(ctx, systemConfig, fqdn, message) - if err != nil { - lastErr = err - continue - } - return response, nil - } - return nil, lastErr -} - -func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { - returned := make(chan struct{}) - defer close(returned) - type queryResult struct { - response *mDNS.Msg - err error - } - results := make(chan queryResult) - startRacer := func(ctx context.Context, fqdn string) { - response, err := t.tryOneName(ctx, systemConfig, fqdn, message) - if err == nil { - if response.Rcode != mDNS.RcodeSuccess { - err = dns.RcodeError(response.Rcode) - } else if len(dns.MessageToAddresses(response)) == 0 { - err = dns.RcodeSuccess - } - } - select { - case results <- queryResult{response, err}: - case <-returned: - } - } - queryCtx, queryCancel := context.WithCancel(ctx) - defer queryCancel() - var nameCount int - for _, fqdn := range systemConfig.nameList(domain) { - nameCount++ - go startRacer(queryCtx, fqdn) - } - var errors []error - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case result := <-results: - if result.err == nil { - return result.response, nil - } - errors = append(errors, result.err) - if len(errors) == nameCount { - return nil, E.Errors(errors...) - } - } - } -} - -func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) { - serverOffset := config.serverOffset() - sLen := uint32(len(config.servers)) - var lastErr error - for i := 0; i < config.attempts; i++ { - for j := uint32(0); j < sLen; j++ { - server := config.servers[(serverOffset+j)%sLen] - question := message.Question[0] - question.Name = fqdn - response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD) - if err != nil { - lastErr = err - continue - } - return response, nil - } - } - return nil, E.Cause(lastErr, fqdn) -} - -func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) { - if server.Port == 0 { - server.Port = 53 - } - request := &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: uint16(rand.Uint32()), - RecursionDesired: true, - AuthenticatedData: ad, - }, - Question: []mDNS.Question{question}, - Compress: true, - } - request.SetEdns0(buf.UDPBufferSize, false) - if !useTCP { - return t.exchangeUDP(ctx, server, request, timeout) - } else { - return t.exchangeTCP(ctx, server, request, timeout) - } -} - -func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { - conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server) - if err != nil { - return nil, err - } - defer conn.Close() - if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { - newDeadline := time.Now().Add(timeout) - if deadline.After(newDeadline) { - deadline = newDeadline - } - conn.SetDeadline(deadline) - } - buffer := buf.Get(buf.UDPBufferSize) - defer buf.Put(buffer) - rawMessage, err := request.PackBuffer(buffer) - if err != nil { - return nil, E.Cause(err, "pack request") - } - _, err = conn.Write(rawMessage) - if err != nil { - if errors.Is(err, syscall.EMSGSIZE) { - return t.exchangeTCP(ctx, server, request, timeout) - } - return nil, E.Cause(err, "write request") - } - n, err := conn.Read(buffer) - if err != nil { - if errors.Is(err, syscall.EMSGSIZE) { - return t.exchangeTCP(ctx, server, request, timeout) - } - return nil, E.Cause(err, "read response") - } - var response mDNS.Msg - err = response.Unpack(buffer[:n]) - if err != nil { - return nil, E.Cause(err, "unpack response") - } - if response.Truncated { - return t.exchangeTCP(ctx, server, request, timeout) - } - return &response, nil -} - -func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { - conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server) - if err != nil { - return nil, err - } - defer conn.Close() - if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { - newDeadline := time.Now().Add(timeout) - if deadline.After(newDeadline) { - deadline = newDeadline - } - conn.SetDeadline(deadline) - } - err = transport.WriteMessage(conn, 0, request) - if err != nil { - return nil, err - } - return transport.ReadMessage(conn) + return t.exchange(ctx, message, question.Name) } diff --git a/dns/transport/local/local_darwin.go b/dns/transport/local/local_darwin.go new file mode 100644 index 00000000..4026cedb --- /dev/null +++ b/dns/transport/local/local_darwin.go @@ -0,0 +1,134 @@ +//go:build darwin + +package local + +import ( + "context" + "errors" + "net" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/hosts" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "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" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +func RegisterTransport(registry *dns.TransportRegistry) { + dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport) +} + +var _ adapter.DNSTransport = (*Transport)(nil) + +type Transport struct { + dns.TransportAdapter + ctx context.Context + logger logger.ContextLogger + hosts *hosts.File + dialer N.Dialer + preferGo bool + fallback bool + dhcpTransport dhcpTransport + resolver net.Resolver +} + +type dhcpTransport interface { + adapter.DNSTransport + Fetch() ([]M.Socksaddr, error) + Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) +} + +func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { + transportDialer, err := dns.NewLocalDialer(ctx, options) + if err != nil { + return nil, err + } + transportAdapter := dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options) + return &Transport{ + TransportAdapter: transportAdapter, + ctx: ctx, + logger: logger, + hosts: hosts.NewFile(hosts.DefaultPath), + dialer: transportDialer, + preferGo: options.PreferGo, + }, nil +} + +func (t *Transport) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + inboundManager := service.FromContext[adapter.InboundManager](t.ctx) + for _, inbound := range inboundManager.Inbounds() { + if inbound.Type() == C.TypeTun { + t.fallback = true + break + } + } + if t.fallback { + t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger) + if t.dhcpTransport != nil { + err := t.dhcpTransport.Start(stage) + if err != nil { + return err + } + } + } + return nil +} + +func (t *Transport) Close() error { + return common.Close( + t.dhcpTransport, + ) +} + +func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + question := message.Question[0] + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name)) + if len(addresses) > 0 { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + } + if !t.fallback { + return t.exchange(ctx, message, question.Name) + } + if t.dhcpTransport != nil { + dhcpTransports, _ := t.dhcpTransport.Fetch() + if len(dhcpTransports) > 0 { + return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports) + } + } + if t.preferGo { + // Assuming the user knows what they are doing, we still execute the query which will fail. + return t.exchange(ctx, message, question.Name) + } + if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { + var network string + if question.Qtype == mDNS.TypeA { + network = "ip4" + } else { + network = "ip6" + } + addresses, err := t.resolver.LookupNetIP(ctx, network, question.Name) + if err != nil { + var dnsError *net.DNSError + if errors.As(err, &dnsError) && dnsError.IsNotFound { + return nil, dns.RcodeRefused + } + return nil, err + } + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil + } + return nil, E.New("only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.") +} diff --git a/dns/transport/local/local_darwin_dhcp.go b/dns/transport/local/local_darwin_dhcp.go new file mode 100644 index 00000000..b228b76a --- /dev/null +++ b/dns/transport/local/local_darwin_dhcp.go @@ -0,0 +1,16 @@ +//go:build darwin && with_dhcp + +package local + +import ( + "context" + + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/dhcp" + "github.com/sagernet/sing-box/log" + N "github.com/sagernet/sing/common/network" +) + +func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport { + return dhcp.NewRawTransport(transportAdapter, ctx, dialer, logger) +} diff --git a/dns/transport/local/local_darwin_nodhcp.go b/dns/transport/local/local_darwin_nodhcp.go new file mode 100644 index 00000000..5ce84690 --- /dev/null +++ b/dns/transport/local/local_darwin_nodhcp.go @@ -0,0 +1,15 @@ +//go:build darwin && !with_dhcp + +package local + +import ( + "context" + + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" + N "github.com/sagernet/sing/common/network" +) + +func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport { + return nil +} diff --git a/dns/transport/local/local_fallback.go b/dns/transport/local/local_fallback.go deleted file mode 100644 index fcc01cc9..00000000 --- a/dns/transport/local/local_fallback.go +++ /dev/null @@ -1,208 +0,0 @@ -package local - -import ( - "context" - "errors" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/service" - - mDNS "github.com/miekg/dns" -) - -func RegisterTransport(registry *dns.TransportRegistry) { - dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewFallbackTransport) -} - -type FallbackTransport struct { - adapter.DNSTransport - ctx context.Context - fallback bool - resolver net.Resolver -} - -func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { - transport, err := NewTransport(ctx, logger, tag, options) - if err != nil { - return nil, err - } - platformInterface := service.FromContext[platform.Interface](ctx) - if platformInterface == nil { - return transport, nil - } - return &FallbackTransport{ - DNSTransport: transport, - ctx: ctx, - }, nil -} - -func (f *FallbackTransport) Start(stage adapter.StartStage) error { - err := f.DNSTransport.Start(stage) - if err != nil { - return err - } - if stage != adapter.StartStatePostStart { - return nil - } - inboundManager := service.FromContext[adapter.InboundManager](f.ctx) - for _, inbound := range inboundManager.Inbounds() { - if inbound.Type() == C.TypeTun { - // platform tun hijacks DNS, so we can only use cgo resolver here - f.fallback = true - break - } - } - return nil -} - -func (f *FallbackTransport) Close() error { - return f.DNSTransport.Close() -} - -func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - if !f.fallback { - return f.DNSTransport.Exchange(ctx, message) - } - question := message.Question[0] - if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { - var network string - if question.Qtype == mDNS.TypeA { - network = "ip4" - } else { - network = "ip6" - } - addresses, err := f.resolver.LookupNetIP(ctx, network, question.Name) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil - } else if question.Qtype == mDNS.TypeNS { - records, err := f.resolver.LookupNS(ctx, question.Name) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - response := &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - } - for _, record := range records { - response.Answer = append(response.Answer, &mDNS.NS{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeNS, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Ns: record.Host, - }) - } - return response, nil - } else if question.Qtype == mDNS.TypeCNAME { - cname, err := f.resolver.LookupCNAME(ctx, question.Name) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - return &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - Answer: []mDNS.RR{ - &mDNS.CNAME{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeCNAME, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Target: cname, - }, - }, - }, nil - } else if question.Qtype == mDNS.TypeTXT { - records, err := f.resolver.LookupTXT(ctx, question.Name) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - return &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - Answer: []mDNS.RR{ - &mDNS.TXT{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeCNAME, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Txt: records, - }, - }, - }, nil - } else if question.Qtype == mDNS.TypeMX { - records, err := f.resolver.LookupMX(ctx, question.Name) - if err != nil { - var dnsError *net.DNSError - if errors.As(err, &dnsError) && dnsError.IsNotFound { - return nil, dns.RcodeRefused - } - return nil, err - } - response := &mDNS.Msg{ - MsgHdr: mDNS.MsgHdr{ - Id: message.Id, - Rcode: mDNS.RcodeSuccess, - Response: true, - }, - Question: []mDNS.Question{question}, - } - for _, record := range records { - response.Answer = append(response.Answer, &mDNS.MX{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: C.DefaultDNSTTL, - }, - Preference: record.Pref, - Mx: record.Host, - }) - } - return response, nil - } else { - return nil, E.New("only A, AAAA, NS, CNAME, TXT, MX queries are supported on current platform when using TUN, please switch to a fixed DNS server.") - } -} diff --git a/dns/transport/local/local_shared.go b/dns/transport/local/local_shared.go new file mode 100644 index 00000000..3b05dac6 --- /dev/null +++ b/dns/transport/local/local_shared.go @@ -0,0 +1,191 @@ +package local + +import ( + "context" + "errors" + "math/rand" + "syscall" + "time" + + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + mDNS "github.com/miekg/dns" +) + +func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + systemConfig := getSystemDNSConfig(t.ctx) + if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) { + return t.exchangeSingleRequest(ctx, systemConfig, message, domain) + } else { + return t.exchangeParallel(ctx, systemConfig, message, domain) + } +} + +func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + var lastErr error + for _, fqdn := range systemConfig.nameList(domain) { + response, err := t.tryOneName(ctx, systemConfig, fqdn, message) + if err != nil { + lastErr = err + continue + } + return response, nil + } + return nil, lastErr +} + +func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) { + returned := make(chan struct{}) + defer close(returned) + type queryResult struct { + response *mDNS.Msg + err error + } + results := make(chan queryResult) + startRacer := func(ctx context.Context, fqdn string) { + response, err := t.tryOneName(ctx, systemConfig, fqdn, message) + if err == nil { + if response.Rcode != mDNS.RcodeSuccess { + err = dns.RcodeError(response.Rcode) + } else if len(dns.MessageToAddresses(response)) == 0 { + err = E.New(fqdn, ": empty result") + } + } + select { + case results <- queryResult{response, err}: + case <-returned: + } + } + queryCtx, queryCancel := context.WithCancel(ctx) + defer queryCancel() + var nameCount int + for _, fqdn := range systemConfig.nameList(domain) { + nameCount++ + go startRacer(queryCtx, fqdn) + } + var errors []error + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result := <-results: + if result.err == nil { + return result.response, nil + } + errors = append(errors, result.err) + if len(errors) == nameCount { + return nil, E.Errors(errors...) + } + } + } +} + +func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) { + serverOffset := config.serverOffset() + sLen := uint32(len(config.servers)) + var lastErr error + for i := 0; i < config.attempts; i++ { + for j := uint32(0); j < sLen; j++ { + server := config.servers[(serverOffset+j)%sLen] + question := message.Question[0] + question.Name = fqdn + response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD) + if err != nil { + lastErr = err + continue + } + return response, nil + } + } + return nil, E.Cause(lastErr, fqdn) +} + +func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) { + if server.Port == 0 { + server.Port = 53 + } + request := &mDNS.Msg{ + MsgHdr: mDNS.MsgHdr{ + Id: uint16(rand.Uint32()), + RecursionDesired: true, + AuthenticatedData: ad, + }, + Question: []mDNS.Question{question}, + Compress: true, + } + request.SetEdns0(buf.UDPBufferSize, false) + if !useTCP { + return t.exchangeUDP(ctx, server, request, timeout) + } else { + return t.exchangeTCP(ctx, server, request, timeout) + } +} + +func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + newDeadline := time.Now().Add(timeout) + if deadline.After(newDeadline) { + deadline = newDeadline + } + conn.SetDeadline(deadline) + } + buffer := buf.Get(buf.UDPBufferSize) + defer buf.Put(buffer) + rawMessage, err := request.PackBuffer(buffer) + if err != nil { + return nil, E.Cause(err, "pack request") + } + _, err = conn.Write(rawMessage) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request, timeout) + } + return nil, E.Cause(err, "write request") + } + n, err := conn.Read(buffer) + if err != nil { + if errors.Is(err, syscall.EMSGSIZE) { + return t.exchangeTCP(ctx, server, request, timeout) + } + return nil, E.Cause(err, "read response") + } + var response mDNS.Msg + err = response.Unpack(buffer[:n]) + if err != nil { + return nil, E.Cause(err, "unpack response") + } + if response.Truncated { + return t.exchangeTCP(ctx, server, request, timeout) + } + return &response, nil +} + +func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server) + if err != nil { + return nil, err + } + defer conn.Close() + if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() { + newDeadline := time.Now().Add(timeout) + if deadline.After(newDeadline) { + deadline = newDeadline + } + conn.SetDeadline(deadline) + } + err = transport.WriteMessage(conn, 0, request) + if err != nil { + return nil, err + } + return transport.ReadMessage(conn) +} diff --git a/dns/transport/local/resolv_darwin_cgo.go b/dns/transport/local/resolv_darwin_cgo.go deleted file mode 100644 index bbe4ccfe..00000000 --- a/dns/transport/local/resolv_darwin_cgo.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build darwin && cgo - -package local - -/* -#include -#include -#include -#include -*/ -import "C" - -import ( - "context" - "time" - - E "github.com/sagernet/sing/common/exceptions" - - "github.com/miekg/dns" -) - -func dnsReadConfig(_ context.Context, _ string) *dnsConfig { - var state C.struct___res_state - if C.res_ninit(&state) != 0 { - return &dnsConfig{ - servers: defaultNS, - search: dnsDefaultSearch(), - ndots: 1, - timeout: 5 * time.Second, - attempts: 2, - err: E.New("libresolv initialization failed"), - } - } - conf := &dnsConfig{ - ndots: 1, - timeout: 5 * time.Second, - attempts: int(state.retry), - } - for i := 0; i < int(state.nscount); i++ { - ns := state.nsaddr_list[i] - addr := C.inet_ntoa(ns.sin_addr) - if addr == nil { - continue - } - conf.servers = append(conf.servers, C.GoString(addr)) - } - for i := 0; ; i++ { - search := state.dnsrch[i] - if search == nil { - break - } - conf.search = append(conf.search, dns.Fqdn(C.GoString(search))) - } - return conf -} diff --git a/dns/transport/local/resolv_unix.go b/dns/transport/local/resolv_unix.go index f77f3553..51512f65 100644 --- a/dns/transport/local/resolv_unix.go +++ b/dns/transport/local/resolv_unix.go @@ -1,4 +1,4 @@ -//go:build !windows && !(darwin && cgo) +//go:build !windows package local diff --git a/dns/transport_manager.go b/dns/transport_manager.go index f41c9f9e..e289ccea 100644 --- a/dns/transport_manager.go +++ b/dns/transport_manager.go @@ -30,7 +30,7 @@ type TransportManager struct { transportByTag map[string]adapter.DNSTransport dependByTag map[string][]string defaultTransport adapter.DNSTransport - defaultTransportFallback adapter.DNSTransport + defaultTransportFallback func() (adapter.DNSTransport, error) fakeIPTransport adapter.FakeIPTransport } @@ -45,7 +45,7 @@ func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransp } } -func (m *TransportManager) Initialize(defaultTransportFallback adapter.DNSTransport) { +func (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.DNSTransport, error)) { m.defaultTransportFallback = defaultTransportFallback } @@ -56,14 +56,27 @@ func (m *TransportManager) Start(stage adapter.StartStage) error { } m.started = true m.stage = stage - transports := m.transports - m.access.Unlock() if stage == adapter.StartStateStart { if m.defaultTag != "" && m.defaultTransport == nil { + m.access.Unlock() return E.New("default DNS server not found: ", m.defaultTag) } - return m.startTransports(m.transports) + if m.defaultTransport == nil { + defaultTransport, err := m.defaultTransportFallback() + if err != nil { + m.access.Unlock() + return E.Cause(err, "default DNS server fallback") + } + m.transports = append(m.transports, defaultTransport) + m.transportByTag[defaultTransport.Tag()] = defaultTransport + m.defaultTransport = defaultTransport + } + transports := m.transports + m.access.Unlock() + return m.startTransports(transports) } else { + transports := m.transports + m.access.Unlock() for _, outbound := range transports { err := adapter.LegacyStart(outbound, stage) if err != nil { @@ -172,11 +185,7 @@ func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) { func (m *TransportManager) Default() adapter.DNSTransport { m.access.RLock() defer m.access.RUnlock() - if m.defaultTransport != nil { - return m.defaultTransport - } else { - return m.defaultTransportFallback - } + return m.defaultTransport } func (m *TransportManager) FakeIP() adapter.FakeIPTransport { diff --git a/option/dns.go b/option/dns.go index 422d7b3b..7a23f2c8 100644 --- a/option/dns.go +++ b/option/dns.go @@ -190,7 +190,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error { } } remoteOptions := RemoteDNSServerOptions{ - LocalDNSServerOptions: LocalDNSServerOptions{ + RawLocalDNSServerOptions: RawLocalDNSServerOptions{ DialerOptions: DialerOptions{ Detour: options.Detour, DomainResolver: &DomainResolveOptions{ @@ -211,7 +211,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error { switch serverType { case C.DNSTypeLocal: o.Type = C.DNSTypeLocal - o.Options = &remoteOptions.LocalDNSServerOptions + o.Options = &remoteOptions.RawLocalDNSServerOptions case C.DNSTypeUDP: o.Type = C.DNSTypeUDP o.Options = &remoteOptions @@ -363,7 +363,7 @@ type HostsDNSServerOptions struct { Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"` } -type LocalDNSServerOptions struct { +type RawLocalDNSServerOptions struct { DialerOptions Legacy bool `json:"-"` LegacyStrategy DomainStrategy `json:"-"` @@ -371,8 +371,13 @@ type LocalDNSServerOptions struct { LegacyClientSubnet netip.Prefix `json:"-"` } +type LocalDNSServerOptions struct { + RawLocalDNSServerOptions + PreferGo bool `json:"prefer_go,omitempty"` +} + type RemoteDNSServerOptions struct { - LocalDNSServerOptions + RawLocalDNSServerOptions DNSServerAddressOptions LegacyAddressResolver string `json:"-"` LegacyAddressStrategy DomainStrategy `json:"-"` From bba92146b165249e2b424ba1782218c3bbbeca95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 19 Aug 2025 21:38:03 +0800 Subject: [PATCH 011/185] Stop using DHCP on iOS and tvOS We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons. --- cmd/internal/build_libbox/main.go | 10 ++++++---- dns/transport/local/local_darwin.go | 30 ++++++++++++++++++----------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 71df1ae4..6f7018d3 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -46,7 +46,7 @@ var ( sharedFlags []string debugFlags []string sharedTags []string - darwinTags []string + macOSTags []string memcTags []string notMemcTags []string debugTags []string @@ -63,7 +63,7 @@ func init() { debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+"-s -w -buildid= -checklinkname=0") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") - darwinTags = append(darwinTags, "with_dhcp") + macOSTags = append(macOSTags, "with_dhcp") memcTags = append(memcTags, "with_tailscale") notMemcTags = append(notMemcTags, "with_low_memory") debugTags = append(debugTags, "debug") @@ -158,7 +158,9 @@ func buildApple() { "-tags-not-macos=with_low_memory", } if !withTailscale { - args = append(args, "-tags-macos="+strings.Join(memcTags, ",")) + args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ",")) + } else { + args = append(args, "-tags-macos="+strings.Join(macOSTags, ",")) } if !debugEnabled { @@ -167,7 +169,7 @@ func buildApple() { args = append(args, debugFlags...) } - tags := append(sharedTags, darwinTags...) + tags := sharedTags if withTailscale { tags = append(tags, memcTags...) } diff --git a/dns/transport/local/local_darwin.go b/dns/transport/local/local_darwin.go index 4026cedb..3f2b424c 100644 --- a/dns/transport/local/local_darwin.go +++ b/dns/transport/local/local_darwin.go @@ -74,12 +74,14 @@ func (t *Transport) Start(stage adapter.StartStage) error { break } } - if t.fallback { - t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger) - if t.dhcpTransport != nil { - err := t.dhcpTransport.Start(stage) - if err != nil { - return err + if !C.IsIos { + if t.fallback { + t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger) + if t.dhcpTransport != nil { + err := t.dhcpTransport.Start(stage) + if err != nil { + return err + } } } } @@ -103,10 +105,12 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, if !t.fallback { return t.exchange(ctx, message, question.Name) } - if t.dhcpTransport != nil { - dhcpTransports, _ := t.dhcpTransport.Fetch() - if len(dhcpTransports) > 0 { - return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports) + if !C.IsIos { + if t.dhcpTransport != nil { + dhcpTransports, _ := t.dhcpTransport.Fetch() + if len(dhcpTransports) > 0 { + return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports) + } } } if t.preferGo { @@ -130,5 +134,9 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, } return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil } - return nil, E.New("only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.") + if C.IsIos { + return nil, E.New("only A and AAAA queries are supported on iOS and tvOS when using NetworkExtension.") + } else { + return nil, E.New("only A and AAAA queries are supported on macOS when using NetworkExtension and DHCP unavailable.") + } } From e3473d3de09abd198eefa6163dbd13bc980a7f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Aug 2025 16:04:13 +0800 Subject: [PATCH 012/185] documentation: Improve `local` DNS server --- docs/configuration/dns/server/local.md | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/configuration/dns/server/local.md b/docs/configuration/dns/server/local.md index debcba98..361c6c5b 100644 --- a/docs/configuration/dns/server/local.md +++ b/docs/configuration/dns/server/local.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [prefer_go](#prefer_go) + !!! question "Since sing-box 1.12.0" # Local @@ -15,6 +19,7 @@ icon: material/new-box { "type": "local", "tag": "", + "prefer_go": false // Dial Fields } @@ -24,10 +29,31 @@ icon: material/new-box ``` !!! info "Difference from legacy local server" - + * The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests. * The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default. +### Fields + +#### prefer_go + +!!! question "Since sing-box 1.13.0" + +When enabled, `local` DNS server will resolve DNS by dialing itself whenever possible. + +Specifically, it disables following behaviors which was added as features in sing-box 1.13.0: + +* On Apple platforms: Use `libresolv` for resolution, as it is the only one that works properly with NetworkExtension + that overrides DNS servers (DHCP is also possible but is not considered). +* On Linux: Resolve through `systemd-resolvd`'s DBus interface when available. + +As a sole exception, it cannot disable the following behavior: + +In the Android graphical client, the `local` DNS server will always resolve DNS through the platform interface, +as there is no other way to obtain upstream DNS servers. + +On devices running Android versions lower than 10, this interface can only resolve IP queries. + ### Dial Fields See [Dial Fields](/configuration/shared/dial/) for details. From 1336987756704375a25158cb34f55ae984045113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Aug 2025 10:57:21 +0800 Subject: [PATCH 013/185] documentation: Remove outdated icons --- docs/configuration/endpoint/index.md | 4 ---- docs/configuration/endpoint/index.zh.md | 4 ---- docs/configuration/endpoint/wireguard.md | 4 ---- docs/configuration/endpoint/wireguard.zh.md | 4 ---- docs/configuration/outbound/hysteria2.md | 4 ---- docs/configuration/outbound/hysteria2.zh.md | 4 ---- 6 files changed, 24 deletions(-) diff --git a/docs/configuration/endpoint/index.md b/docs/configuration/endpoint/index.md index 59101e75..b409a783 100644 --- a/docs/configuration/endpoint/index.md +++ b/docs/configuration/endpoint/index.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.11.0" # Endpoint diff --git a/docs/configuration/endpoint/index.zh.md b/docs/configuration/endpoint/index.zh.md index 6f31d3d6..f7e71b75 100644 --- a/docs/configuration/endpoint/index.zh.md +++ b/docs/configuration/endpoint/index.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.11.0 起" # 端点 diff --git a/docs/configuration/endpoint/wireguard.md b/docs/configuration/endpoint/wireguard.md index 65bb6929..dc3b8228 100644 --- a/docs/configuration/endpoint/wireguard.md +++ b/docs/configuration/endpoint/wireguard.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.11.0" ### Structure diff --git a/docs/configuration/endpoint/wireguard.zh.md b/docs/configuration/endpoint/wireguard.zh.md index cf820580..1935135f 100644 --- a/docs/configuration/endpoint/wireguard.zh.md +++ b/docs/configuration/endpoint/wireguard.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.11.0 起" ### 结构 diff --git a/docs/configuration/outbound/hysteria2.md b/docs/configuration/outbound/hysteria2.md index 77063fb4..dc0a4965 100644 --- a/docs/configuration/outbound/hysteria2.md +++ b/docs/configuration/outbound/hysteria2.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "Changes in sing-box 1.11.0" :material-plus: [server_ports](#server_ports) diff --git a/docs/configuration/outbound/hysteria2.zh.md b/docs/configuration/outbound/hysteria2.zh.md index 0c5a631e..d2a8598f 100644 --- a/docs/configuration/outbound/hysteria2.zh.md +++ b/docs/configuration/outbound/hysteria2.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! quote "sing-box 1.11.0 中的更改" :material-plus: [server_ports](#server_ports) From 2be8a45f148ed0ed730af72919de87e7d22b86a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Aug 2025 19:31:47 +0800 Subject: [PATCH 014/185] Fix rule-set format --- common/srs/binary.go | 14 ++-- option/rule.go | 80 +++++++++---------- option/rule_dns.go | 84 ++++++++++---------- option/rule_set.go | 46 +++++------ route/rule/rule_default_interface_address.go | 2 +- route/rule/rule_interface_address.go | 2 +- route/rule/rule_network_interface_address.go | 2 +- 7 files changed, 117 insertions(+), 113 deletions(-) diff --git a/common/srs/binary.go b/common/srs/binary.go index 96b578f5..0c93c284 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -235,7 +235,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea case ruleItemNetworkIsConstrained: rule.NetworkIsConstrained = true case ruleItemNetworkInterfaceAddress: - rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) + rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) var size uint64 size, err = binary.ReadUvarint(reader) if err != nil { @@ -247,7 +247,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea if err != nil { return } - var value []badoption.Prefixable + var value []*badoption.Prefixable var prefixCount uint64 prefixCount, err = binary.ReadUvarint(reader) if err != nil { @@ -259,12 +259,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea if err != nil { return } - value = append(value, badoption.Prefixable(prefix)) + value = append(value, common.Ptr(badoption.Prefixable(prefix))) } rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value) } case ruleItemDefaultInterfaceAddress: - var value []badoption.Prefixable + var value []*badoption.Prefixable var prefixCount uint64 prefixCount, err = binary.ReadUvarint(reader) if err != nil { @@ -276,7 +276,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea if err != nil { return } - value = append(value, badoption.Prefixable(prefix)) + value = append(value, common.Ptr(badoption.Prefixable(prefix))) } rule.DefaultInterfaceAddress = value case ruleItemFinal: @@ -437,6 +437,10 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen if err != nil { return err } + _, err = varbin.WriteUvarint(writer, uint64(len(entry.Value))) + if err != nil { + return err + } for _, rawPrefix := range entry.Value { err = writePrefix(writer, rawPrefix.Build(netip.Prefix{})) if err != nil { diff --git a/option/rule.go b/option/rule.go index 44927e3a..3e7fd877 100644 --- a/option/rule.go +++ b/option/rule.go @@ -67,46 +67,46 @@ func (r Rule) IsValid() bool { } type RawDefaultRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Client badoption.Listable[string] `json:"client,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"` - NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` - DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` - PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Client badoption.Listable[string] `json:"client,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` + PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index bbab993c..dbc16578 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -68,48 +68,48 @@ func (r DNSRule) IsValid() bool { } type RawDefaultDNSRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - IPAcceptAny bool `json:"ip_accept_any,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - Outbound badoption.Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"` - NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` - DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + IPAcceptAny bool `json:"ip_accept_any,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + Outbound badoption.Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index fa839e35..b0634228 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -182,29 +182,29 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"` - DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"` + DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"` Invert bool `json:"invert,omitempty"` diff --git a/route/rule/rule_default_interface_address.go b/route/rule/rule_default_interface_address.go index 940d3a06..2d7fdebe 100644 --- a/route/rule/rule_default_interface_address.go +++ b/route/rule/rule_default_interface_address.go @@ -17,7 +17,7 @@ type DefaultInterfaceAddressItem struct { interfaceAddresses []netip.Prefix } -func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[badoption.Prefixable]) *DefaultInterfaceAddressItem { +func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[*badoption.Prefixable]) *DefaultInterfaceAddressItem { item := &DefaultInterfaceAddressItem{ interfaceMonitor: networkManager.InterfaceMonitor(), interfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)), diff --git a/route/rule/rule_interface_address.go b/route/rule/rule_interface_address.go index 53ece683..d4c75d38 100644 --- a/route/rule/rule_interface_address.go +++ b/route/rule/rule_interface_address.go @@ -19,7 +19,7 @@ type InterfaceAddressItem struct { description string } -func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]]) *InterfaceAddressItem { +func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]]) *InterfaceAddressItem { item := &InterfaceAddressItem{ networkManager: networkManager, interfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()), diff --git a/route/rule/rule_network_interface_address.go b/route/rule/rule_network_interface_address.go index c365be3b..c699c593 100644 --- a/route/rule/rule_network_interface_address.go +++ b/route/rule/rule_network_interface_address.go @@ -20,7 +20,7 @@ type NetworkInterfaceAddressItem struct { description string } -func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) *NetworkInterfaceAddressItem { +func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) *NetworkInterfaceAddressItem { item := &NetworkInterfaceAddressItem{ networkManager: networkManager, interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()), From 044eb728cbf08ea322e0a4269034ac455b7c57a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 18 Aug 2025 22:38:50 +0800 Subject: [PATCH 015/185] Fix legacy DNS config --- option/dns.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/option/dns.go b/option/dns.go index 7a23f2c8..4c1ac208 100644 --- a/option/dns.go +++ b/option/dns.go @@ -211,7 +211,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error { switch serverType { case C.DNSTypeLocal: o.Type = C.DNSTypeLocal - o.Options = &remoteOptions.RawLocalDNSServerOptions + o.Options = &LocalDNSServerOptions{ + RawLocalDNSServerOptions: remoteOptions.RawLocalDNSServerOptions, + } case C.DNSTypeUDP: o.Type = C.DNSTypeUDP o.Options = &remoteOptions From 387b42c9c262014c25c34259277599b7891aa7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 19 Aug 2025 21:34:15 +0800 Subject: [PATCH 016/185] Remove use of ldflags `-checklinkname=0` on darwin --- .github/workflows/build.yml | 19 +------------------ cmd/internal/build_libbox/main.go | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0e4da8c0..5ef5ae20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,7 +149,7 @@ jobs: TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build - if: matrix.os != 'darwin' && matrix.os != 'android' + if: matrix.os != 'android' run: | set -xeuo pipefail mkdir -p dist @@ -165,23 +165,6 @@ jobs: GOMIPS: ${{ matrix.gomips }} GOMIPS64: ${{ matrix.gomips }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Build darwin - if: matrix.os == 'darwin' - run: | - set -xeuo pipefail - mkdir -p dist - go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ - ./cmd/sing-box - env: - CGO_ENABLED: "0" - GOOS: ${{ matrix.os }} - GOARCH: ${{ matrix.arch }} - GO386: ${{ matrix.go386 }} - GOARM: ${{ matrix.goarm }} - GOMIPS: ${{ matrix.gomips }} - GOMIPS64: ${{ matrix.gomips }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build Android if: matrix.os == 'android' run: | diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 6f7018d3..0f4db850 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -59,8 +59,8 @@ func init() { if err != nil { currentTag = "unknown" } - sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") - debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+"-s -w -buildid= -checklinkname=0") + sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") + debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") macOSTags = append(macOSTags, "with_dhcp") @@ -106,17 +106,19 @@ func buildAndroid() { "-libname=box", } + if !debugEnabled { + sharedFlags[3] = sharedFlags[3] + " -checklinkname=0" + args = append(args, sharedFlags...) + } else { + debugFlags[1] = debugFlags[1] + " -checklinkname=0" + args = append(args, debugFlags...) + } + tags := append(sharedTags, memcTags...) if debugEnabled { tags = append(tags, debugTags...) } - if !debugEnabled { - args = append(args, sharedFlags...) - } else { - args = append(args, debugFlags...) - } - args = append(args, "-tags", strings.Join(tags, ",")) args = append(args, "./experimental/libbox") From a5e09fcd4374d29907d4fc56507f7b72b8cc4511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 19 Aug 2025 21:52:29 +0800 Subject: [PATCH 017/185] documentation: Update behavior of `local` DNS server on darwin --- docs/configuration/dns/server/local.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/configuration/dns/server/local.md b/docs/configuration/dns/server/local.md index 361c6c5b..9556cb1d 100644 --- a/docs/configuration/dns/server/local.md +++ b/docs/configuration/dns/server/local.md @@ -43,16 +43,18 @@ When enabled, `local` DNS server will resolve DNS by dialing itself whenever pos Specifically, it disables following behaviors which was added as features in sing-box 1.13.0: -* On Apple platforms: Use `libresolv` for resolution, as it is the only one that works properly with NetworkExtension - that overrides DNS servers (DHCP is also possible but is not considered). -* On Linux: Resolve through `systemd-resolvd`'s DBus interface when available. +1. On Apple platforms: Attempt to resolve A/AAAA requests using `getaddrinfo` in NetworkExtension. +2. On Linux: Resolve through `systemd-resolvd`'s DBus interface when available. As a sole exception, it cannot disable the following behavior: -In the Android graphical client, the `local` DNS server will always resolve DNS through the platform interface, -as there is no other way to obtain upstream DNS servers. +1. In the Android graphical client, +`local` will always resolve DNS through the platform interface, +as there is no other way to obtain upstream DNS servers; +On devices running Android versions lower than 10, this interface can only resolve A/AAAA requests. -On devices running Android versions lower than 10, this interface can only resolve IP queries. +2. On macOS, `local` will try DHCP first in Network Extension, since DHCP respects DIal Fields, +it will not be disabled by `prefer_go`. ### Dial Fields From 44fafcef73b71503ba60405754ccc7efd07b6470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 26 Aug 2025 17:33:17 +0800 Subject: [PATCH 018/185] Fix resolve using resolved --- dns/transport/local/local_resolved_linux.go | 114 +++++++++++++++++--- 1 file changed, 97 insertions(+), 17 deletions(-) diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go index 1d841a29..3c9bf0c4 100644 --- a/dns/transport/local/local_resolved_linux.go +++ b/dns/transport/local/local_resolved_linux.go @@ -2,15 +2,20 @@ package local import ( "context" + "errors" "os" "sync" + "sync/atomic" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/service/resolved" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/godbus/dbus/v5" @@ -18,11 +23,18 @@ import ( ) type DBusResolvedResolver struct { - logger logger.ContextLogger - interfaceMonitor tun.DefaultInterfaceMonitor - systemBus *dbus.Conn - resoledObject common.TypedValue[dbus.BusObject] - closeOnce sync.Once + ctx context.Context + logger logger.ContextLogger + interfaceMonitor tun.DefaultInterfaceMonitor + interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] + systemBus *dbus.Conn + resoledObject atomic.Pointer[ResolvedObject] + closeOnce sync.Once +} + +type ResolvedObject struct { + dbus.BusObject + InterfaceIndex int32 } func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { @@ -35,6 +47,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso return nil, err } return &DBusResolvedResolver{ + ctx: ctx, logger: logger, interfaceMonitor: interfaceMonitor, systemBus: systemBus, @@ -43,6 +56,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso func (t *DBusResolvedResolver) Start() error { t.updateStatus() + t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface) err := t.systemBus.BusObject().AddMatchSignal( "org.freedesktop.DBus", "NameOwnerChanged", @@ -58,6 +72,9 @@ func (t *DBusResolvedResolver) Start() error { func (t *DBusResolvedResolver) Close() error { t.closeOnce.Do(func() { + if t.interfaceCallback != nil { + t.interfaceMonitor.UnregisterCallback(t.interfaceCallback) + } if t.systemBus != nil { _ = t.systemBus.Close() } @@ -66,26 +83,27 @@ func (t *DBusResolvedResolver) Close() error { } func (t *DBusResolvedResolver) Object() any { - return t.resoledObject.Load() + return common.PtrOrNil(t.resoledObject.Load()) } func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - defaultInterface := t.interfaceMonitor.DefaultInterface() - if defaultInterface == nil { - return nil, E.New("missing default interface") - } question := message.Question[0] - call := object.(*dbus.Object).CallWithContext( + resolvedObject := object.(*ResolvedObject) + call := resolvedObject.CallWithContext( ctx, "org.freedesktop.resolve1.Manager.ResolveRecord", 0, - int32(defaultInterface.Index), + resolvedObject.InterfaceIndex, question.Name, question.Qclass, question.Qtype, uint64(0), ) if call.Err != nil { + var dbusError dbus.Error + if errors.As(call.Err, &dbusError) && dbusError.Name == "org.freedesktop.resolve1.NoNameServers" { + t.updateStatus() + } return nil, E.Cause(call.Err, " resolve record via resolved") } var ( @@ -137,14 +155,76 @@ func (t *DBusResolvedResolver) loopUpdateStatus() { } func (t *DBusResolvedResolver) updateStatus() { - dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") - err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err + dbusObject, err := t.checkResolved(context.Background()) + oldValue := t.resoledObject.Swap(dbusObject) if err != nil { - if t.resoledObject.Swap(nil) != nil { + var dbusErr dbus.Error + if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwnerCould" { + t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable")) + } + if oldValue != nil { t.logger.Debug("systemd-resolved service is gone") } return + } else if oldValue == nil { + t.logger.Debug("using systemd-resolved service as resolver") } - t.resoledObject.Store(dbusObject) - t.logger.Debug("using systemd-resolved service as resolver") +} + +func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObject, error) { + dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") + err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err + if err != nil { + return nil, err + } + defaultInterface := t.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + call := dbusObject.(*dbus.Object).CallWithContext( + ctx, + "org.freedesktop.resolve1.Manager.GetLink", + 0, + int32(defaultInterface.Index), + ) + if call.Err != nil { + return nil, err + } + var linkPath dbus.ObjectPath + err = call.Store(&linkPath) + if err != nil { + return nil, err + } + linkObject := t.systemBus.Object("org.freedesktop.resolve1", linkPath) + if linkObject == nil { + return nil, E.New("missing link object for default interface") + } + dnsProp, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS") + if err != nil { + return nil, err + } + var linkDNS []resolved.LinkDNS + err = dnsProp.Store(&linkDNS) + if err != nil { + return nil, err + } + if len(linkDNS) == 0 { + for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() { + if inbound.Type() == C.TypeTun { + return nil, E.New("No appropriate name servers or networks for name found") + } + } + return &ResolvedObject{ + BusObject: dbusObject, + }, nil + } else { + return &ResolvedObject{ + BusObject: dbusObject, + InterfaceIndex: int32(defaultInterface.Index), + }, nil + } +} + +func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) { + t.updateStatus() } From f84129ca79c030581a97641fc15eab119acdd5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 17 Feb 2025 22:00:57 +0800 Subject: [PATCH 019/185] Add proxy support for ICMP echo request --- adapter/outbound.go | 8 ++ adapter/router.go | 4 +- common/dialer/default.go | 8 ++ constant/rule.go | 1 + docs/configuration/route/rule.md | 13 ++- docs/configuration/route/rule.zh.md | 13 ++- docs/configuration/route/rule_action.md | 16 +++ docs/configuration/route/rule_action.zh.md | 16 +++ go.mod | 50 ++++----- go.sum | 104 +++++++++--------- option/rule_action.go | 1 + protocol/direct/outbound.go | 18 +++- protocol/group/selector.go | 10 ++ protocol/group/urltest.go | 16 +++ protocol/tailscale/endpoint.go | 77 ++++++++++++-- protocol/tun/inbound.go | 20 +++- protocol/wireguard/endpoint.go | 27 ++++- protocol/wireguard/outbound.go | 8 +- route/route.go | 56 ++++++++-- route/rule/rule_action.go | 5 +- transport/wireguard/device.go | 10 +- transport/wireguard/device_nat.go | 103 ++++++++++++++++++ transport/wireguard/device_stack.go | 118 +++++++++++++++------ transport/wireguard/device_system.go | 46 ++++++-- transport/wireguard/device_system_stack.go | 67 +++++++++++- transport/wireguard/endpoint.go | 24 ++++- 26 files changed, 676 insertions(+), 163 deletions(-) create mode 100644 transport/wireguard/device_nat.go diff --git a/adapter/outbound.go b/adapter/outbound.go index 517aa0fe..91fb9c65 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -3,9 +3,11 @@ package adapter import ( "context" "net/netip" + "time" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" N "github.com/sagernet/sing/common/network" ) @@ -20,10 +22,16 @@ type Outbound interface { } type OutboundWithPreferredRoutes interface { + Outbound PreferredDomain(domain string) bool PreferredAddress(address netip.Addr) bool } +type DirectRouteOutbound interface { + Outbound + NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) +} + type OutboundRegistry interface { option.OutboundOptionsRegistry CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) diff --git a/adapter/router.go b/adapter/router.go index 0b7c8f4f..522a0d9d 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -6,8 +6,10 @@ import ( "net" "net/http" "sync" + "time" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-tun" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" @@ -19,7 +21,7 @@ import ( type Router interface { Lifecycle ConnectionRouter - PreMatch(metadata InboundContext) error + PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) ConnectionRouterEx RuleSet(tag string) (RuleSet, bool) NeedWIFIState() bool diff --git a/common/dialer/default.go b/common/dialer/default.go index 08111625..7eac3729 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -315,6 +315,14 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd } } +func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer { + if !destination.Is6() { + return dialerFromTCPDialer(d.dialer6) + } else { + return dialerFromTCPDialer(d.dialer4) + } +} + func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { if strategy == nil { strategy = d.networkStrategy diff --git a/constant/rule.go b/constant/rule.go index 71441450..b565a39d 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -40,4 +40,5 @@ const ( const ( RuleActionRejectMethodDefault = "default" RuleActionRejectMethodDrop = "drop" + RuleActionRejectMethodReply = "reply" ) diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 89eecaaf..a6e89d7b 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -7,7 +7,8 @@ icon: material/new-box :material-plus: [interface_address](#interface_address) :material-plus: [network_interface_address](#network_interface_address) :material-plus: [default_interface_address](#default_interface_address) - :material-plus: [preferred_by](#preferred_by) + :material-plus: [preferred_by](#preferred_by) + :material-alert: [network](#network) !!! quote "Changes in sing-box 1.11.0" @@ -226,7 +227,15 @@ Sniffed client type, see [Protocol Sniff](/configuration/route/sniff/) for detai #### network -`tcp` or `udp`. +!!! quote "Changes in sing-box 1.13.0" + + Since sing-box 1.13.0, you can match ICMP echo (ping) requests via the new `icmp` network. + + Such traffic originates from `TUN`, `WireGuard`, and `Tailscale` inbounds and can be routed to `Direct`, `WireGuard`, and `Tailscale` outbounds. + +Match network type. + +`tcp`, `udp` or `icmp`. #### domain diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 100344da..a90607d4 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -7,7 +7,8 @@ icon: material/new-box :material-plus: [interface_address](#interface_address) :material-plus: [network_interface_address](#network_interface_address) :material-plus: [default_interface_address](#default_interface_address) - :material-plus: [preferred_by](#preferred_by) + :material-plus: [preferred_by](#preferred_by) + :material-alert: [network](#network) !!! quote "sing-box 1.11.0 中的更改" @@ -223,7 +224,15 @@ icon: material/new-box #### network -`tcp` 或 `udp`。 +!!! quote "sing-box 1.13.0 中的更改" + + 自 sing-box 1.13.0 起,您可以通过新的 `icmp` 网络匹配 ICMP 回显(ping)请求。 + + 此类流量源自 `TUN`、`WireGuard` 和 `Tailscale` 入站,并可路由至 `Direct`、`WireGuard` 和 `Tailscale` 出站。 + +匹配网络类型。 + +`tcp`、`udp` 或 `icmp`。 #### domain diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index c975af2b..a57de60f 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-alert: [reject](#reject) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [tls_fragment](#tls_fragment) @@ -42,6 +46,10 @@ See `route-options` fields below. ### reject +!!! quote "Changes in sing-box 1.13.0" + + Since sing-box 1.13.0, you can reject (or directly reply to) ICMP echo (ping) requests using `reject` action. + ```json { "action": "reject", @@ -58,9 +66,17 @@ For non-tun connections and already established connections, will just be closed #### method +For TCP and UDP connections: + - `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets. - `drop`: Drop packets. +For ICMP echo requests: + +- `default`: Reply with ICMP host unreachable. +- `drop`: Drop packets. +- `reply`: Reply with ICMP echo reply. + #### no_drop If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 0081e827..98ea1227 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-alert: [reject](#reject) + !!! quote "sing-box 1.12.0 中的更改" :material-plus: [tls_fragment](#tls_fragment) @@ -38,6 +42,10 @@ icon: material/new-box ### reject +!!! quote "sing-box 1.13.0 中的更改" + + 自 sing-box 1.13.0 起,您可以通过 `reject` 动作拒绝(或直接回复)ICMP 回显(ping)请求。 + ```json { "action": "reject", @@ -54,9 +62,17 @@ icon: material/new-box #### method +对于 TCP 和 UDP 连接: + - `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。 - `drop`: 丢弃数据包。 +对于 ICMP 回显请求: + +- `default`: 回复 ICMP 主机不可达。 +- `drop`: 丢弃数据包。 +- `reply`: 回复以 ICMP 回显应答。 + #### no_drop 如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。 diff --git a/go.mod b/go.mod index f8e34fac..5d5d6414 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sagernet/sing-box -go 1.23.1 +go 1.24.7 require ( github.com/anytls/sing-anytls v0.0.11 @@ -25,30 +25,30 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.8 - github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb - github.com/sagernet/quic-go v0.52.0-sing-box-mod.3 - github.com/sagernet/sing v0.7.14 + github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 + github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 + github.com/sagernet/sing v0.8.0-beta.6 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.5.2 + github.com/sagernet/sing-quic v0.6.0-beta.3 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.7.3 + github.com/sagernet/sing-tun v0.8.0-beta.11 github.com/sagernet/sing-vmess v0.2.7 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2 - github.com/sagernet/wireguard-go v0.0.1-beta.7 + github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 + github.com/sagernet/wireguard-go v0.0.2-beta.1 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.9.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/vishvananda/netns v0.0.5 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.41.0 - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 - golang.org/x/mod v0.27.0 - golang.org/x/net v0.43.0 - golang.org/x/sys v0.35.0 + golang.org/x/crypto v0.42.0 + golang.org/x/exp v0.0.0-20250911091902-df9299821621 + golang.org/x/mod v0.28.0 + golang.org/x/net v0.44.0 + golang.org/x/sys v0.36.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 @@ -63,7 +63,6 @@ require ( github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect @@ -73,8 +72,8 @@ require ( github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gaissmai/bart v0.11.1 // indirect - github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect + github.com/gaissmai/bart v0.18.0 // indirect + github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect @@ -84,16 +83,13 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/illarion/gonotify/v2 v2.0.3 // indirect + github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/libdns/libdns v1.1.0 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect @@ -110,23 +106,23 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect - github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect + github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.37.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect diff --git a/go.sum b/go.sum index 5c1ee5a7..35610419 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= @@ -40,16 +38,16 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= +github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= +github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -75,22 +73,16 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= +github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= +github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= 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-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU= @@ -102,8 +94,6 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ= github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8= @@ -159,36 +149,36 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo= github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= -github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38= -github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= +github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 h1:x/t3XqWshOlWqRuumpvbUvjtEr/6mJuBXAVovPefbUg= +github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.52.0-sing-box-mod.3 h1:ySqffGm82rPqI1TUPqmtHIYd12pfEGScygnOxjTL56w= -github.com/sagernet/quic-go v0.52.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= -github.com/sagernet/sing v0.7.14 h1:5QQRDCUvYNOMyVp3LuK/hYEBAIv0VsbD3x/l9zH467s= -github.com/sagernet/sing v0.7.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 h1:12pJN/zdpRltLG8l8JA65QYy/a+Mz938yAN3ZQUinbo= +github.com/sagernet/quic-go v0.54.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= +github.com/sagernet/sing v0.8.0-beta.6 h1:GXv1j1xWHihx6ptyOXh0yp4jUqJoNjCqD8d+AI9rnLU= +github.com/sagernet/sing v0.8.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.5.2 h1:I3vlfRImhr0uLwRS3b3ib70RMG9FcXtOKKUDz3eKRWc= -github.com/sagernet/sing-quic v0.5.2/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI= +github.com/sagernet/sing-quic v0.6.0-beta.3 h1:Z2vt49f9vNtHc9BbF9foI859n4+NAOV3gBeB1LuzL1Q= +github.com/sagernet/sing-quic v0.6.0-beta.3/go.mod h1:2/swrSS6wG6MyQA5Blq31VEWitHgBju+yZE8cPK1J5I= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.7.3 h1:MFnAir+l24ElEyxdfwtY8mqvUUL9nPnL9TDYLkOmVes= -github.com/sagernet/sing-tun v0.7.3/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM= +github.com/sagernet/sing-tun v0.8.0-beta.11 h1:xVi8VcVkvz2o+3v1PLv5MOkFpiVCwjLjucVlmigDi5c= +github.com/sagernet/sing-tun v0.8.0-beta.11/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2 h1:MO7s4ni2bSfAOhcan2rdQSWCztkMXmqyg6jYPZp8bEE= -github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= -github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= -github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= +github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos= +github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw= +github.com/sagernet/wireguard-go v0.0.2-beta.1 h1:Zy7+dXpdViaRhP1pjtcMRfcbb7mWSxduCKzyEYbSJvk= +github.com/sagernet/wireguard-go v0.0.2-beta.1/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -197,14 +187,12 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= @@ -215,6 +203,8 @@ github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+y github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= @@ -255,21 +245,21 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= -golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= -golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= 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.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -277,20 +267,20 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -312,6 +302,8 @@ gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa 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= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= diff --git a/option/rule_action.go b/option/rule_action.go index 914edb84..e28b58eb 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -282,6 +282,7 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error { case "", C.RuleActionRejectMethodDefault: r.Method = C.RuleActionRejectMethodDefault case C.RuleActionRejectMethodDrop: + case C.RuleActionRejectMethodReply: default: return E.New("unknown reject method: " + r.Method) } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 58cdb002..1f24f0c9 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -13,6 +13,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -28,10 +31,12 @@ var ( _ N.ParallelDialer = (*Outbound)(nil) _ dialer.ParallelNetworkDialer = (*Outbound)(nil) _ dialer.DirectDialer = (*Outbound)(nil) + _ adapter.DirectRouteOutbound = (*Outbound)(nil) ) type Outbound struct { outbound.Adapter + ctx context.Context logger logger.ContextLogger dialer dialer.ParallelInterfaceDialer domainStrategy C.DomainStrategy @@ -57,7 +62,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, err } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), + ctx: ctx, logger: logger, //nolint:staticcheck domainStrategy: C.DomainStrategy(options.DomainStrategy), @@ -145,6 +151,16 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return conn, nil } +func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(h.ctx) + destination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout) + if err != nil { + return nil, err + } + h.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() diff --git a/protocol/group/selector.go b/protocol/group/selector.go index 439283bd..f3f7377b 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -3,6 +3,7 @@ package group import ( "context" "net" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -10,6 +11,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -174,6 +176,14 @@ func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, } } +func (s *Selector) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + selected := s.selected.Load() + if !common.Contains(selected.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag()) + } + return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) +} + func RealTag(detour adapter.Outbound) string { if group, isGroup := detour.(adapter.OutboundGroup); isGroup { return group.Now() diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 4bdef9fb..26967279 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -14,6 +14,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" E "github.com/sagernet/sing/common/exceptions" @@ -170,6 +171,21 @@ func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose) } +func (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + s.group.Touch() + selected := s.group.selectedOutboundTCP + if selected == nil { + selected, _ = s.group.Select(N.NetworkTCP) + } + if selected == nil { + return nil, E.New("missing supported outbound") + } + if !common.Contains(selected.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag()) + } + return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) +} + type URLTestGroup struct { ctx context.Context router adapter.Router diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 1e67c392..ab979088 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -21,6 +21,7 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" "github.com/sagernet/gvisor/pkg/tcpip/header" "github.com/sagernet/gvisor/pkg/tcpip/stack" + "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" "github.com/sagernet/sing-box/adapter" @@ -30,7 +31,9 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/control" @@ -57,7 +60,10 @@ import ( "go4.org/netipx" ) -var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) +var ( + _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + _ adapter.DirectRouteOutbound = (*Endpoint)(nil) +) func init() { version.SetVersion("sing-box " + C.Version) @@ -77,6 +83,7 @@ type Endpoint struct { platformInterface platform.Interface server *tsnet.Server stack *stack.Stack + icmpForwarder *tun.ICMPForwarder filter *atomic.Pointer[filter.Filter] onReconfigHook wgengine.ReconfigListener @@ -177,7 +184,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL }, } return &Endpoint{ - Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil), + Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil), ctx: ctx, router: router, logger: logger, @@ -242,9 +249,12 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { return gonet.TranslateNetstackError(gErr) } ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket) - udpForwarder := tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout) - ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout).HandlePacket) + icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) t.stack = ipStack + t.icmpForwarder = icmpForwarder localBackend := t.server.ExportLocalBackend() perfs := &ipn.MaskedPrefs{ @@ -433,7 +443,7 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return udpConn, nil } -func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { +func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { tsFilter := t.filter.Load() if tsFilter != nil { var ipProto ipproto.Proto @@ -442,22 +452,41 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina ipProto = ipproto.TCP case N.NetworkUDP: ipProto = ipproto.UDP + case N.NetworkICMP: + if !destination.IsIPv6() { + ipProto = ipproto.ICMPv4 + } else { + ipProto = ipproto.ICMPv6 + } } response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto) switch response { case filter.Drop: - return syscall.ECONNRESET + return nil, syscall.ECONNREFUSED case filter.DropSilently: - return tun.ErrDrop + return nil, tun.ErrDrop } } - return t.router.PreMatch(adapter.InboundContext{ + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := t.router.PreMatch(adapter.InboundContext{ Inbound: t.Tag(), InboundType: t.Type(), + IPVersion: ipVersion, Network: network, Source: source, Destination: destination, - }) + }, routeContext, timeout) + if err != nil { + if !rule.IsRejected(err) { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + return routeDestination, err } func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { @@ -500,6 +529,27 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } +func (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + inet4Address, inet6Address := t.server.TailscaleIPs() + if metadata.Destination.Addr.Is4() && !inet4Address.IsValid() || metadata.Destination.Addr.Is6() && !inet6Address.IsValid() { + return nil, E.New("Tailscale is not ready yet") + } + ctx := log.ContextWithNewID(t.ctx) + destination, err := ping.ConnectGVisor( + ctx, t.logger, + metadata.Source.Addr, metadata.Destination.Addr, + routeContext, + t.stack, + inet4Address, inet6Address, + timeout, + ) + if err != nil { + return nil, err + } + t.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + func (t *Endpoint) PreferredDomain(domain string) bool { routeDomains := t.routeDomains.Load() if routeDomains == nil { @@ -527,6 +577,15 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) { return } + var inet4Address, inet6Address netip.Addr + for _, address := range cfg.Addresses { + if address.Addr().Is4() { + inet4Address = address.Addr() + } else if address.Addr().Is6() { + inet6Address = address.Addr() + } + } + t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) t.cfg = cfg t.dnsCfg = dnsCfg diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index fc69309c..3f013598 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -18,6 +18,7 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -454,15 +455,28 @@ func (t *Inbound) Close() error { ) } -func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { - return t.router.PreMatch(adapter.InboundContext{ +func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := t.router.PreMatch(adapter.InboundContext{ Inbound: t.tag, InboundType: C.TypeTun, + IPVersion: ipVersion, Network: network, Source: source, Destination: destination, InboundOptions: t.inboundOptions, - }) + }, routeContext, timeout) + if err != nil { + if !rule.IsRejected(err) { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + return routeDestination, err } func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 207670c2..811c6bb4 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -12,7 +12,9 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/transport/wireguard" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" @@ -40,7 +42,7 @@ type Endpoint struct { func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) { ep := &Endpoint{ - Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), ctx: ctx, router: router, dnsRouter: service.FromContext[adapter.DNSRouter](ctx), @@ -124,14 +126,27 @@ func (w *Endpoint) Close() error { return w.endpoint.Close() } -func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { - return w.router.PreMatch(adapter.InboundContext{ +func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := w.router.PreMatch(adapter.InboundContext{ Inbound: w.Tag(), InboundType: w.Type(), + IPVersion: ipVersion, Network: network, Source: source, Destination: destination, - }) + }, routeContext, timeout) + if err != nil { + if !rule.IsRejected(err) { + w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + return routeDestination, err } func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { @@ -220,3 +235,7 @@ func (w *Endpoint) PreferredDomain(domain string) bool { func (w *Endpoint) PreferredAddress(address netip.Addr) bool { return w.endpoint.Lookup(address) != nil } + +func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + return w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout) +} diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index fa58d959..5b08c6a7 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -4,6 +4,7 @@ import ( "context" "net" "net/netip" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -13,6 +14,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/wireguard" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -42,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL deprecated.Report(ctx, deprecated.OptionWireGuardGSO) } outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), ctx: ctx, dnsRouter: service.FromContext[adapter.DNSRouter](ctx), logger: logger, @@ -168,3 +170,7 @@ func (o *Outbound) PreferredDomain(domain string) bool { func (o *Outbound) PreferredAddress(address netip.Addr) bool { return o.endpoint.Lookup(address) != nil } + +func (o *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + return o.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout) +} diff --git a/route/route.go b/route/route.go index 0213f354..7e87e97f 100644 --- a/route/route.go +++ b/route/route.go @@ -16,6 +16,7 @@ import ( "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-mux" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -113,6 +114,9 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad } case *R.RuleActionReject: buf.ReleaseMulti(buffers) + if action.Method == C.RuleActionRejectMethodReply { + return E.New("reject method `reply` is not supported for TCP connections") + } return action.Error(ctx) case *R.RuleActionHijackDNS: for _, buffer := range buffers { @@ -228,6 +232,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m } case *R.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) + if action.Method == C.RuleActionRejectMethodReply { + return E.New("reject method `reply` is not supported for UDP connections") + } return action.Error(ctx) case *R.RuleActionHijackDNS: return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose) @@ -259,19 +266,47 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m return nil } -func (r *Router) PreMatch(metadata adapter.InboundContext) error { +func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil) if err != nil { - return err + return nil, err } - if selectedRule == nil { - return nil + if selectedRule != nil { + switch action := selectedRule.Action().(type) { + case *R.RuleActionReject: + switch metadata.Network { + case N.NetworkTCP: + if action.Method == C.RuleActionRejectMethodReply { + return nil, E.New("reject method `reply` is not supported for TCP connections") + } + case N.NetworkUDP: + if action.Method == C.RuleActionRejectMethodReply { + return nil, E.New("reject method `reply` is not supported for UDP connections") + } + } + return nil, action.Error(context.Background()) + case *R.RuleActionRoute: + if routeContext == nil { + return nil, nil + } + outbound, loaded := r.outbound.Outbound(action.Outbound) + if !loaded { + return nil, E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(outbound.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound) + } + return outbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) + } } - rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject) - if !isReject { - return nil + if selectedRule != nil || metadata.Network != N.NetworkICMP { + return nil, nil } - return rejectAction.Error(context.Background()) + defaultOutbound := r.outbound.Default() + if !common.Contains(defaultOutbound.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag()) + } + return defaultOutbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) } func (r *Router) matchRule( @@ -464,7 +499,7 @@ match: fatalErr = newErr return } - } else { + } else if metadata.Network != N.NetworkICMP { selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match @@ -478,8 +513,7 @@ match: actionType := currentRule.Action().Type() if actionType == C.RuleActionTypeRoute || actionType == C.RuleActionTypeReject || - actionType == C.RuleActionTypeHijackDNS || - (actionType == C.RuleActionTypeSniff && preMatch) { + actionType == C.RuleActionTypeHijackDNS { selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 62d5410a..5c08109a 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -6,7 +6,6 @@ import ( "net/netip" "strings" "sync" - "syscall" "time" "github.com/sagernet/sing-box/adapter" @@ -325,9 +324,11 @@ func (r *RuleActionReject) Error(ctx context.Context) error { var returnErr error switch r.Method { case C.RuleActionRejectMethodDefault: - returnErr = &RejectedError{syscall.ECONNREFUSED} + returnErr = &RejectedError{tun.ErrReset} case C.RuleActionRejectMethodDrop: return &RejectedError{tun.ErrDrop} + case C.RuleActionRejectMethodReply: + return nil default: panic(F.ToString("unknown reject method: ", r.Method)) } diff --git a/transport/wireguard/device.go b/transport/wireguard/device.go index 7a17b8f3..4dd615c5 100644 --- a/transport/wireguard/device.go +++ b/transport/wireguard/device.go @@ -5,6 +5,7 @@ import ( "net/netip" "time" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" @@ -17,6 +18,8 @@ type Device interface { N.Dialer Start() error SetDevice(device *device.Device) + Inet4Address() netip.Addr + Inet6Address() netip.Addr } type DeviceOptions struct { @@ -35,9 +38,14 @@ type DeviceOptions struct { func NewDevice(options DeviceOptions) (Device, error) { if !options.System { return newStackDevice(options) - } else if options.Handler == nil { + } else if !tun.WithGVisor { return newSystemDevice(options) } else { return newSystemStackDevice(options) } } + +type NatDevice interface { + Device + CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) +} diff --git a/transport/wireguard/device_nat.go b/transport/wireguard/device_nat.go new file mode 100644 index 00000000..e5a28c1b --- /dev/null +++ b/transport/wireguard/device_nat.go @@ -0,0 +1,103 @@ +package wireguard + +import ( + "context" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/logger" +) + +var _ Device = (*natDeviceWrapper)(nil) + +type natDeviceWrapper struct { + Device + ctx context.Context + logger logger.ContextLogger + packetOutbound chan *buf.Buffer + rewriter *ping.Rewriter + buffer [][]byte +} + +func NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Device) NatDevice { + wrapper := &natDeviceWrapper{ + Device: upstream, + ctx: ctx, + logger: logger, + packetOutbound: make(chan *buf.Buffer, 256), + rewriter: ping.NewRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()), + } + return wrapper +} + +func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { + select { + case packet := <-d.packetOutbound: + defer packet.Release() + sizes[0] = copy(bufs[0][offset:], packet.Bytes()) + return 1, nil + default: + } + return d.Device.Read(bufs, sizes, offset) +} + +func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) { + for _, buffer := range bufs { + handled, err := d.rewriter.WriteBack(buffer[offset:]) + if handled { + if err != nil { + return 0, err + } + } else { + d.buffer = append(d.buffer, buffer) + } + } + if len(d.buffer) > 0 { + _, err := d.Device.Write(d.buffer, offset) + if err != nil { + return 0, err + } + d.buffer = d.buffer[:0] + } + return 0, nil +} + +func (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(d.ctx) + session := tun.DirectRouteSession{ + Source: metadata.Source.Addr, + Destination: metadata.Destination.Addr, + } + d.rewriter.CreateSession(session, routeContext) + d.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return &natDestination{device: d, session: session}, nil +} + +var _ tun.DirectRouteDestination = (*natDestination)(nil) + +type natDestination struct { + device *natDeviceWrapper + session tun.DirectRouteSession + closed atomic.Bool +} + +func (d *natDestination) WritePacket(buffer *buf.Buffer) error { + d.device.rewriter.RewritePacket(buffer.Bytes()) + d.device.packetOutbound <- buffer + return nil +} + +func (d *natDestination) Close() error { + d.closed.Store(true) + d.device.rewriter.DeleteSession(d.session) + return nil +} + +func (d *natDestination) IsClosed() bool { + return d.closed.Load() +} diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index f9440f02..a190baba 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -5,7 +5,9 @@ package wireguard import ( "context" "net" + "net/netip" "os" + "time" "github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/tcpip" @@ -14,9 +16,14 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/network/ipv4" "github.com/sagernet/gvisor/pkg/tcpip/network/ipv6" "github.com/sagernet/gvisor/pkg/tcpip/stack" + "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" + "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -24,30 +31,40 @@ import ( wgTun "github.com/sagernet/wireguard-go/tun" ) -var _ Device = (*stackDevice)(nil) +var _ NatDevice = (*stackDevice)(nil) type stackDevice struct { - stack *stack.Stack - mtu uint32 - events chan wgTun.Event - outbound chan *stack.PacketBuffer - done chan struct{} - dispatcher stack.NetworkDispatcher - addr4 tcpip.Address - addr6 tcpip.Address + ctx context.Context + logger log.ContextLogger + stack *stack.Stack + mtu uint32 + events chan wgTun.Event + outbound chan *stack.PacketBuffer + packetOutbound chan *buf.Buffer + done chan struct{} + dispatcher stack.NetworkDispatcher + inet4Address netip.Addr + inet6Address netip.Addr } func newStackDevice(options DeviceOptions) (*stackDevice, error) { tunDevice := &stackDevice{ - mtu: options.MTU, - events: make(chan wgTun.Event, 1), - outbound: make(chan *stack.PacketBuffer, 256), - done: make(chan struct{}), + ctx: options.Context, + logger: options.Logger, + mtu: options.MTU, + events: make(chan wgTun.Event, 1), + outbound: make(chan *stack.PacketBuffer, 256), + packetOutbound: make(chan *buf.Buffer, 256), + done: make(chan struct{}), } - ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice)) + ipStack, err := tun.NewGVisorStackWithOptions((*wireEndpoint)(tunDevice), stack.NICOptions{}, true) if err != nil { return nil, err } + var ( + inet4Address netip.Addr + inet6Address netip.Addr + ) for _, prefix := range options.Address { addr := tun.AddressFromAddr(prefix.Addr()) protoAddr := tcpip.ProtocolAddress{ @@ -57,10 +74,12 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) { }, } if prefix.Addr().Is4() { - tunDevice.addr4 = addr + inet4Address = prefix.Addr() + tunDevice.inet4Address = inet4Address protoAddr.Protocol = ipv4.ProtocolNumber } else { - tunDevice.addr6 = addr + inet6Address = prefix.Addr() + tunDevice.inet6Address = inet6Address protoAddr.Protocol = ipv6.ProtocolNumber } gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) @@ -72,6 +91,10 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) { if options.Handler != nil { ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) + icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout) + icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) } return tunDevice, nil } @@ -87,11 +110,17 @@ func (w *stackDevice) DialContext(ctx context.Context, network string, destinati } var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { + if !w.inet4Address.IsValid() { + return nil, E.New("missing IPv4 local address") + } networkProtocol = header.IPv4ProtocolNumber - bind.Addr = w.addr4 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } else { + if !w.inet6Address.IsValid() { + return nil, E.New("missing IPv6 local address") + } networkProtocol = header.IPv6ProtocolNumber - bind.Addr = w.addr6 + bind.Addr = tun.AddressFromAddr(w.inet6Address) } switch N.NetworkName(network) { case N.NetworkTCP: @@ -118,10 +147,10 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) var networkProtocol tcpip.NetworkProtocolNumber if destination.IsIPv4() { networkProtocol = header.IPv4ProtocolNumber - bind.Addr = w.addr4 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } else { networkProtocol = header.IPv6ProtocolNumber - bind.Addr = w.addr6 + bind.Addr = tun.AddressFromAddr(w.inet4Address) } udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol) if err != nil { @@ -130,6 +159,14 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr) return udpConn, nil } +func (w *stackDevice) Inet4Address() netip.Addr { + return w.inet4Address +} + +func (w *stackDevice) Inet6Address() netip.Addr { + return w.inet6Address +} + func (w *stackDevice) SetDevice(device *device.Device) { } @@ -144,20 +181,24 @@ func (w *stackDevice) File() *os.File { func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { select { - case packetBuffer, ok := <-w.outbound: + case packet, ok := <-w.outbound: if !ok { return 0, os.ErrClosed } - defer packetBuffer.DecRef() - p := bufs[0] - p = p[offset:] - n := 0 - for _, slice := range packetBuffer.AsSlices() { - n += copy(p[n:], slice) + defer packet.DecRef() + var copyN int + /*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) { + copyN += copy(bufs[0][offset+copyN:], view.AsSlice()) + })*/ + for _, view := range packet.AsSlices() { + copyN += copy(bufs[0][offset+copyN:], view) } - sizes[0] = n - count = 1 - return + sizes[0] = copyN + return 1, nil + case packet := <-w.packetOutbound: + defer packet.Release() + sizes[0] = copy(bufs[0][offset:], packet.Bytes()) + return 1, nil case <-w.done: return 0, os.ErrClosed } @@ -217,6 +258,23 @@ func (w *stackDevice) BatchSize() int { return 1 } +func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(w.ctx) + destination, err := ping.ConnectGVisor( + ctx, w.logger, + metadata.Source.Addr, metadata.Destination.Addr, + routeContext, + w.stack, + w.inet4Address, w.inet6Address, + timeout, + ) + if err != nil { + return nil, err + } + w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + var _ stack.LinkEndpoint = (*wireEndpoint)(nil) type wireEndpoint stackDevice diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index fa54f332..162a5cbf 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -22,22 +22,42 @@ import ( var _ Device = (*systemDevice)(nil) type systemDevice struct { - options DeviceOptions - dialer N.Dialer - device tun.Tun - batchDevice tun.LinuxTUN - events chan wgTun.Event - closeOnce sync.Once + options DeviceOptions + dialer N.Dialer + device tun.Tun + batchDevice tun.LinuxTUN + events chan wgTun.Event + closeOnce sync.Once + inet4Address netip.Addr + inet6Address netip.Addr } func newSystemDevice(options DeviceOptions) (*systemDevice, error) { if options.Name == "" { options.Name = tun.CalculateInterfaceName("wg") } + var inet4Address netip.Addr + var inet6Address netip.Addr + if len(options.Address) > 0 { + if prefix := common.Find(options.Address, func(it netip.Prefix) bool { + return it.Addr().Is4() + }); prefix.IsValid() { + inet4Address = prefix.Addr() + } + } + if len(options.Address) > 0 { + if prefix := common.Find(options.Address, func(it netip.Prefix) bool { + return it.Addr().Is6() + }); prefix.IsValid() { + inet6Address = prefix.Addr() + } + } return &systemDevice{ - options: options, - dialer: options.CreateDialer(options.Name), - events: make(chan wgTun.Event, 1), + options: options, + dialer: options.CreateDialer(options.Name), + events: make(chan wgTun.Event, 1), + inet4Address: inet4Address, + inet6Address: inet6Address, }, nil } @@ -49,6 +69,14 @@ func (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr return w.dialer.ListenPacket(ctx, destination) } +func (w *systemDevice) Inet4Address() netip.Addr { + return w.inet4Address +} + +func (w *systemDevice) Inet6Address() netip.Addr { + return w.inet6Address +} + func (w *systemDevice) SetDevice(device *device.Device) { } diff --git a/transport/wireguard/device_system_stack.go b/transport/wireguard/device_system_stack.go index 4249e53e..94fd6f4f 100644 --- a/transport/wireguard/device_system_stack.go +++ b/transport/wireguard/device_system_stack.go @@ -3,16 +3,26 @@ package wireguard import ( + "context" "net/netip" + "time" "github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/tcpip" "github.com/sagernet/gvisor/pkg/tcpip/header" + "github.com/sagernet/gvisor/pkg/tcpip/network/ipv4" + "github.com/sagernet/gvisor/pkg/tcpip/network/ipv6" "github.com/sagernet/gvisor/pkg/tcpip/stack" + "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/wireguard-go/device" ) @@ -20,6 +30,8 @@ var _ Device = (*systemStackDevice)(nil) type systemStackDevice struct { *systemDevice + ctx context.Context + logger logger.ContextLogger stack *stack.Stack endpoint *deviceEndpoint writeBufs [][]byte @@ -34,13 +46,45 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) { mtu: options.MTU, done: make(chan struct{}), } - ipStack, err := tun.NewGVisorStack(endpoint) + ipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true) if err != nil { return nil, err } - ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) - ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) + var ( + inet4Address netip.Addr + inet6Address netip.Addr + ) + for _, prefix := range options.Address { + addr := tun.AddressFromAddr(prefix.Addr()) + protoAddr := tcpip.ProtocolAddress{ + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: addr, + PrefixLen: prefix.Bits(), + }, + } + if prefix.Addr().Is4() { + inet4Address = prefix.Addr() + protoAddr.Protocol = ipv4.ProtocolNumber + } else { + inet6Address = prefix.Addr() + protoAddr.Protocol = ipv6.ProtocolNumber + } + gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{}) + if gErr != nil { + return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String()) + } + } + if options.Handler != nil { + ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket) + icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout) + icmpForwarder.SetLocalAddresses(inet4Address, inet6Address) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) + ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) + } return &systemStackDevice{ + ctx: options.Context, + logger: options.Logger, systemDevice: system, stack: ipStack, endpoint: endpoint, @@ -116,6 +160,23 @@ func (w *systemStackDevice) writeStack(packet []byte) bool { return true } +func (w *systemStackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + ctx := log.ContextWithNewID(w.ctx) + destination, err := ping.ConnectGVisor( + ctx, w.logger, + metadata.Source.Addr, metadata.Destination.Addr, + routeContext, + w.stack, + w.inet4Address, w.inet6Address, + timeout, + ) + if err != nil { + return nil, err + } + w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString()) + return destination, nil +} + type deviceEndpoint struct { mtu uint32 done chan struct{} diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 2adf7832..12718b91 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -10,8 +10,11 @@ import ( "os" "reflect" "strings" + "time" "unsafe" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -31,6 +34,7 @@ type Endpoint struct { ipcConf string allowedAddress []netip.Prefix tunDevice Device + natDevice NatDevice device *device.Device allowedIPs *device.AllowedIPs pause pause.Manager @@ -114,12 +118,17 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) { if err != nil { return nil, E.Cause(err, "create WireGuard device") } + natDevice, isNatDevice := tunDevice.(NatDevice) + if !isNatDevice { + natDevice = NewNATDevice(options.Context, options.Logger, tunDevice) + } return &Endpoint{ options: options, peers: peers, ipcConf: ipcConf, allowedAddress: allowedAddresses, tunDevice: tunDevice, + natDevice: natDevice, }, nil } @@ -179,7 +188,13 @@ func (e *Endpoint) Start(resolve bool) error { e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) }, } - wgDevice := device.NewDevice(e.options.Context, e.tunDevice, bind, logger, e.options.Workers) + var deviceInput Device + if e.natDevice != nil { + deviceInput = e.natDevice + } else { + deviceInput = e.tunDevice + } + wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers) e.tunDevice.SetDevice(wgDevice) ipcConf := e.ipcConf for _, peer := range e.peers { @@ -229,6 +244,13 @@ func (e *Endpoint) Lookup(address netip.Addr) *device.Peer { return e.allowedIPs.Lookup(address.AsSlice()) } +func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + if e.natDevice == nil { + return nil, os.ErrInvalid + } + return e.natDevice.CreateDestination(metadata, routeContext, timeout) +} + func (e *Endpoint) onPauseUpdated(event int) { switch event { case pause.EventDevicePaused, pause.EventNetworkPause: From 107f92381b870217d3e0b1406b530f7af3531b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 7 Sep 2025 21:03:32 +0800 Subject: [PATCH 020/185] Add support for kTLS Reference: https://gitlab.com/go-extension/tls --- .github/workflows/build.yml | 4 +- Dockerfile | 2 +- cmd/internal/build_libbox/main.go | 8 +- common/badtls/raw_conn.go | 176 +++++++++++++ common/badtls/raw_half_conn.go | 121 +++++++++ common/badtls/read_wait.go | 123 ++------- common/badtls/read_wait_stub.go | 2 +- common/badtls/read_wait_utls.go | 36 --- common/badtls/registry.go | 62 +++++ common/badtls/registry_utls.go | 56 ++++ common/badversion/version.go | 42 ++- common/badversion/version_test.go | 10 +- common/ktls/ktls.go | 106 ++++++++ common/ktls/ktls_alert.go | 80 ++++++ common/ktls/ktls_cipher_suites_linux.go | 326 +++++++++++++++++++++++ common/ktls/ktls_close.go | 67 +++++ common/ktls/ktls_const.go | 24 ++ common/ktls/ktls_handshake_messages.go | 238 +++++++++++++++++ common/ktls/ktls_key_update.go | 173 +++++++++++++ common/ktls/ktls_linux.go | 329 ++++++++++++++++++++++++ common/ktls/ktls_prf.go | 24 ++ common/ktls/ktls_read.go | 292 +++++++++++++++++++++ common/ktls/ktls_read_wait.go | 41 +++ common/ktls/ktls_stub.go | 15 ++ common/ktls/ktls_write.go | 154 +++++++++++ common/tls/client.go | 13 +- common/tls/ktls.go | 67 +++++ common/tls/reality_client.go | 7 +- common/tls/reality_server.go | 9 +- common/tls/server.go | 2 +- common/tls/std_client.go | 36 ++- common/tls/std_server.go | 15 +- common/tls/utls_client.go | 30 ++- dns/transport/https.go | 2 +- dns/transport/quic/http3.go | 4 +- dns/transport/quic/quic.go | 2 +- dns/transport/tls.go | 2 +- docs/configuration/shared/tls.md | 56 +++- docs/configuration/shared/tls.zh.md | 57 ++++ option/tls.go | 4 + protocol/anytls/outbound.go | 2 +- protocol/http/outbound.go | 2 +- protocol/hysteria/outbound.go | 2 +- protocol/hysteria2/outbound.go | 2 +- protocol/naive/inbound.go | 4 + protocol/shadowtls/outbound.go | 4 +- protocol/tailscale/dns_transport.go | 2 +- protocol/trojan/outbound.go | 2 +- protocol/tuic/outbound.go | 2 +- protocol/vless/outbound.go | 2 +- protocol/vmess/outbound.go | 2 +- release/local/debug.sh | 2 +- release/local/install.sh | 2 +- release/local/reinstall.sh | 2 +- route/conn.go | 36 +-- service/derp/service.go | 4 +- service/resolved/transport.go | 4 +- transport/sip003/v2ray.go | 3 +- transport/trojan/protocol.go | 8 + 59 files changed, 2682 insertions(+), 222 deletions(-) create mode 100644 common/badtls/raw_conn.go create mode 100644 common/badtls/raw_half_conn.go delete mode 100644 common/badtls/read_wait_utls.go create mode 100644 common/badtls/registry.go create mode 100644 common/badtls/registry_utls.go create mode 100644 common/ktls/ktls.go create mode 100644 common/ktls/ktls_alert.go create mode 100644 common/ktls/ktls_cipher_suites_linux.go create mode 100644 common/ktls/ktls_close.go create mode 100644 common/ktls/ktls_const.go create mode 100644 common/ktls/ktls_handshake_messages.go create mode 100644 common/ktls/ktls_key_update.go create mode 100644 common/ktls/ktls_linux.go create mode 100644 common/ktls/ktls_prf.go create mode 100644 common/ktls/ktls_read.go create mode 100644 common/ktls/ktls_read_wait.go create mode 100644 common/ktls/ktls_stub.go create mode 100644 common/ktls/ktls_write.go create mode 100644 common/tls/ktls.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ef5ae20..d637a6c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -154,7 +154,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "0" @@ -174,7 +174,7 @@ jobs: export CXX="${CC}++" mkdir -p dist GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "1" diff --git a/Dockerfile b/Dockerfile index c12b6b4c..dd749bc4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN set -ex \ && go build -v -trimpath -tags \ "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \ -o /go/bin/sing-box \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box FROM --platform=$TARGETPLATFORM alpine AS dist LABEL maintainer="nekohasekai " diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 0f4db850..483bebed 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -59,8 +59,8 @@ func init() { if err != nil { currentTag = "unknown" } - sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") - debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) + sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") + debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") macOSTags = append(macOSTags, "with_dhcp") @@ -107,10 +107,10 @@ func buildAndroid() { } if !debugEnabled { - sharedFlags[3] = sharedFlags[3] + " -checklinkname=0" + // sharedFlags[3] = sharedFlags[3] + " -checklinkname=0" args = append(args, sharedFlags...) } else { - debugFlags[1] = debugFlags[1] + " -checklinkname=0" + // debugFlags[1] = debugFlags[1] + " -checklinkname=0" args = append(args, debugFlags...) } diff --git a/common/badtls/raw_conn.go b/common/badtls/raw_conn.go new file mode 100644 index 00000000..1029d66b --- /dev/null +++ b/common/badtls/raw_conn.go @@ -0,0 +1,176 @@ +//go:build go1.25 && !without_badtls + +package badtls + +import ( + "bytes" + "os" + "reflect" + "sync/atomic" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/tls" +) + +type RawConn struct { + pointer unsafe.Pointer + methods *Methods + + IsClient *bool + IsHandshakeComplete *atomic.Bool + Vers *uint16 + CipherSuite *uint16 + + RawInput *bytes.Buffer + Input *bytes.Reader + Hand *bytes.Buffer + + CloseNotifySent *bool + CloseNotifyErr *error + + In *RawHalfConn + Out *RawHalfConn + + BytesSent *int64 + PacketsSent *int64 + + ActiveCall *atomic.Int32 + Tmp *[16]byte +} + +func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) { + var ( + pointer unsafe.Pointer + methods *Methods + loaded bool + ) + for _, tlsCreator := range methodRegistry { + pointer, methods, loaded = tlsCreator(rawTLSConn) + if loaded { + break + } + } + if !loaded { + return nil, os.ErrInvalid + } + + conn := &RawConn{ + pointer: pointer, + methods: methods, + } + + rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn)) + + rawIsClient := rawConn.FieldByName("isClient") + if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool { + return nil, E.New("invalid Conn.isClient") + } + conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr())) + + rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete") + if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.isHandshakeComplete") + } + conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr())) + + rawVers := rawConn.FieldByName("vers") + if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 { + return nil, E.New("invalid Conn.vers") + } + conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr())) + + rawCipherSuite := rawConn.FieldByName("cipherSuite") + if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 { + return nil, E.New("invalid Conn.cipherSuite") + } + conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr())) + + rawRawInput := rawConn.FieldByName("rawInput") + if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.rawInput") + } + conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr())) + + rawInput := rawConn.FieldByName("input") + if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.input") + } + conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr())) + + rawHand := rawConn.FieldByName("hand") + if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.hand") + } + conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr())) + + rawCloseNotifySent := rawConn.FieldByName("closeNotifySent") + if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool { + return nil, E.New("invalid Conn.closeNotifySent") + } + conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr())) + + rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr") + if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface { + return nil, E.New("invalid Conn.closeNotifyErr") + } + conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr())) + + rawIn := rawConn.FieldByName("in") + if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.in") + } + halfIn, err := NewRawHalfConn(rawIn, methods) + if err != nil { + return nil, E.Cause(err, "invalid Conn.in") + } + conn.In = halfIn + + rawOut := rawConn.FieldByName("out") + if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.out") + } + halfOut, err := NewRawHalfConn(rawOut, methods) + if err != nil { + return nil, E.Cause(err, "invalid Conn.out") + } + conn.Out = halfOut + + rawBytesSent := rawConn.FieldByName("bytesSent") + if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 { + return nil, E.New("invalid Conn.bytesSent") + } + conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr())) + + rawPacketsSent := rawConn.FieldByName("packetsSent") + if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 { + return nil, E.New("invalid Conn.packetsSent") + } + conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr())) + + rawActiveCall := rawConn.FieldByName("activeCall") + if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct { + return nil, E.New("invalid Conn.activeCall") + } + conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr())) + + rawTmp := rawConn.FieldByName("tmp") + if !rawTmp.IsValid() || rawTmp.Kind() != reflect.Array || rawTmp.Len() != 16 || rawTmp.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("invalid Conn.tmp") + } + conn.Tmp = (*[16]byte)(unsafe.Pointer(rawTmp.UnsafeAddr())) + + return conn, nil +} + +func (c *RawConn) ReadRecord() error { + return c.methods.readRecord(c.pointer) +} + +func (c *RawConn) HandlePostHandshakeMessage() error { + return c.methods.handlePostHandshakeMessage(c.pointer) +} + +func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) { + return c.methods.writeRecordLocked(c.pointer, typ, data) +} diff --git a/common/badtls/raw_half_conn.go b/common/badtls/raw_half_conn.go new file mode 100644 index 00000000..dd5e249e --- /dev/null +++ b/common/badtls/raw_half_conn.go @@ -0,0 +1,121 @@ +//go:build go1.25 && !without_badtls + +package badtls + +import ( + "hash" + "reflect" + "sync" + "unsafe" + + E "github.com/sagernet/sing/common/exceptions" +) + +type RawHalfConn struct { + pointer unsafe.Pointer + methods *Methods + *sync.Mutex + Err *error + Version *uint16 + Cipher *any + Seq *[8]byte + ScratchBuf *[13]byte + TrafficSecret *[]byte + Mac *hash.Hash + RawKey *[]byte + RawIV *[]byte + RawMac *[]byte +} + +func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) { + halfConn := &RawHalfConn{ + pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()), + methods: methods, + } + + rawMutex := rawHalfConn.FieldByName("Mutex") + if !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct { + return nil, E.New("badtls: invalid halfConn.Mutex") + } + halfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr())) + + rawErr := rawHalfConn.FieldByName("err") + if !rawErr.IsValid() || rawErr.Kind() != reflect.Interface { + return nil, E.New("badtls: invalid halfConn.err") + } + halfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr())) + + rawVersion := rawHalfConn.FieldByName("version") + if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 { + return nil, E.New("badtls: invalid halfConn.version") + } + halfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr())) + + rawCipher := rawHalfConn.FieldByName("cipher") + if !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface { + return nil, E.New("badtls: invalid halfConn.cipher") + } + halfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr())) + + rawSeq := rawHalfConn.FieldByName("seq") + if !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.seq") + } + halfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr())) + + rawScratchBuf := rawHalfConn.FieldByName("scratchBuf") + if !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.scratchBuf") + } + halfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr())) + + rawTrafficSecret := rawHalfConn.FieldByName("trafficSecret") + if !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.trafficSecret") + } + halfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr())) + + rawMac := rawHalfConn.FieldByName("mac") + if !rawMac.IsValid() || rawMac.Kind() != reflect.Interface { + return nil, E.New("badtls: invalid halfConn.mac") + } + halfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr())) + + rawKey := rawHalfConn.FieldByName("rawKey") + if rawKey.IsValid() { + if /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.rawKey") + } + halfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr())) + + rawIV := rawHalfConn.FieldByName("rawIV") + if !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.rawIV") + } + halfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr())) + + rawMAC := rawHalfConn.FieldByName("rawMac") + if !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 { + return nil, E.New("badtls: invalid halfConn.rawMac") + } + halfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr())) + } + + return halfConn, nil +} + +func (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) { + return hc.methods.decrypt(hc.pointer, record) +} + +func (hc *RawHalfConn) SetErrorLocked(err error) error { + return hc.methods.setErrorLocked(hc.pointer, err) +} + +func (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) { + hc.methods.setTrafficSecret(hc.pointer, suite, level, secret) +} + +func (hc *RawHalfConn) ExplicitNonceLen() int { + return hc.methods.explicitNonceLen(hc.pointer) +} diff --git a/common/badtls/read_wait.go b/common/badtls/read_wait.go index 9508a7e3..d18b4c0e 100644 --- a/common/badtls/read_wait.go +++ b/common/badtls/read_wait.go @@ -1,18 +1,9 @@ -//go:build go1.21 && !without_badtls +//go:build go1.25 && !without_badtls package badtls import ( - "bytes" - "context" - "net" - "os" - "reflect" - "sync" - "unsafe" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/tls" ) @@ -21,63 +12,21 @@ var _ N.ReadWaiter = (*ReadWaitConn)(nil) type ReadWaitConn struct { tls.Conn - halfAccess *sync.Mutex - rawInput *bytes.Buffer - input *bytes.Reader - hand *bytes.Buffer - readWaitOptions N.ReadWaitOptions - tlsReadRecord func() error - tlsHandlePostHandshakeMessage func() error + rawConn *RawConn + readWaitOptions N.ReadWaitOptions } func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) { - var ( - loaded bool - tlsReadRecord func() error - tlsHandlePostHandshakeMessage func() error - ) - for _, tlsCreator := range tlsRegistry { - loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn) - if loaded { - break - } + if _, isReadWaitConn := conn.(N.ReadWaiter); isReadWaitConn { + return conn, nil } - if !loaded { - return nil, os.ErrInvalid + rawConn, err := NewRawConn(conn) + if err != nil { + return nil, err } - rawConn := reflect.Indirect(reflect.ValueOf(conn)) - rawHalfConn := rawConn.FieldByName("in") - if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid half conn") - } - rawHalfMutex := rawHalfConn.FieldByName("Mutex") - if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid half mutex") - } - halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr())) - rawRawInput := rawConn.FieldByName("rawInput") - if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid raw input") - } - rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr())) - rawInput0 := rawConn.FieldByName("input") - if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid input") - } - input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr())) - rawHand := rawConn.FieldByName("hand") - if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct { - return nil, E.New("badtls: invalid hand") - } - hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr())) return &ReadWaitConn{ - Conn: conn, - halfAccess: halfAccess, - rawInput: rawInput, - input: input, - hand: hand, - tlsReadRecord: tlsReadRecord, - tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage, + Conn: conn, + rawConn: rawConn, }, nil } @@ -87,36 +36,36 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy } func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) { - err = c.HandshakeContext(context.Background()) - if err != nil { - return - } - c.halfAccess.Lock() - defer c.halfAccess.Unlock() - for c.input.Len() == 0 { - err = c.tlsReadRecord() + //err = c.HandshakeContext(context.Background()) + //if err != nil { + // return + //} + c.rawConn.In.Lock() + defer c.rawConn.In.Unlock() + for c.rawConn.Input.Len() == 0 { + err = c.rawConn.ReadRecord() if err != nil { return } - for c.hand.Len() > 0 { - err = c.tlsHandlePostHandshakeMessage() + for c.rawConn.Hand.Len() > 0 { + err = c.rawConn.HandlePostHandshakeMessage() if err != nil { return } } } buffer = c.readWaitOptions.NewBuffer() - n, err := c.input.Read(buffer.FreeBytes()) + n, err := c.rawConn.Input.Read(buffer.FreeBytes()) if err != nil { buffer.Release() return } buffer.Truncate(n) - if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 && - // recordType(c.rawInput.Bytes()[0]) == recordTypeAlert { - c.rawInput.Bytes()[0] == 21 { - _ = c.tlsReadRecord() + if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 && + // recordType(c.RawInput.Bytes()[0]) == recordTypeAlert { + c.rawConn.RawInput.Bytes()[0] == 21 { + _ = c.rawConn.ReadRecord() // return n, err // will be io.EOF on closeNotify } @@ -131,25 +80,3 @@ func (c *ReadWaitConn) Upstream() any { func (c *ReadWaitConn) ReaderReplaceable() bool { return true } - -var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) - -func init() { - tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - tlsConn, loaded := conn.(*tls.STDConn) - if !loaded { - return - } - return true, func() error { - return stdTLSReadRecord(tlsConn) - }, func() error { - return stdTLSHandlePostHandshakeMessage(tlsConn) - } - }) -} - -//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord -func stdTLSReadRecord(c *tls.STDConn) error - -//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage -func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error diff --git a/common/badtls/read_wait_stub.go b/common/badtls/read_wait_stub.go index c5c9946f..4bd4fc60 100644 --- a/common/badtls/read_wait_stub.go +++ b/common/badtls/read_wait_stub.go @@ -1,4 +1,4 @@ -//go:build !go1.21 || without_badtls +//go:build !go1.25 || without_badtls package badtls diff --git a/common/badtls/read_wait_utls.go b/common/badtls/read_wait_utls.go deleted file mode 100644 index 1facd30b..00000000 --- a/common/badtls/read_wait_utls.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build go1.21 && !without_badtls && with_utls - -package badtls - -import ( - "net" - _ "unsafe" - - "github.com/metacubex/utls" -) - -func init() { - tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { - switch tlsConn := conn.(type) { - case *tls.UConn: - return true, func() error { - return utlsReadRecord(tlsConn.Conn) - }, func() error { - return utlsHandlePostHandshakeMessage(tlsConn.Conn) - } - case *tls.Conn: - return true, func() error { - return utlsReadRecord(tlsConn) - }, func() error { - return utlsHandlePostHandshakeMessage(tlsConn) - } - } - return - }) -} - -//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord -func utlsReadRecord(c *tls.Conn) error - -//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage -func utlsHandlePostHandshakeMessage(c *tls.Conn) error diff --git a/common/badtls/registry.go b/common/badtls/registry.go new file mode 100644 index 00000000..cc11a16e --- /dev/null +++ b/common/badtls/registry.go @@ -0,0 +1,62 @@ +//go:build go1.25 && !without_badtls + +package badtls + +import ( + "crypto/tls" + "net" + "unsafe" +) + +type Methods struct { + readRecord func(c unsafe.Pointer) error + handlePostHandshakeMessage func(c unsafe.Pointer) error + writeRecordLocked func(c unsafe.Pointer, typ uint16, data []byte) (int, error) + + setErrorLocked func(hc unsafe.Pointer, err error) error + decrypt func(hc unsafe.Pointer, record []byte) ([]byte, uint8, error) + setTrafficSecret func(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte) + explicitNonceLen func(hc unsafe.Pointer) int +} + +var methodRegistry []func(conn net.Conn) (unsafe.Pointer, *Methods, bool) + +func init() { + methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) { + tlsConn, loaded := conn.(*tls.Conn) + if !loaded { + return nil, nil, false + } + return unsafe.Pointer(tlsConn), &Methods{ + readRecord: stdTLSReadRecord, + handlePostHandshakeMessage: stdTLSHandlePostHandshakeMessage, + writeRecordLocked: stdWriteRecordLocked, + + setErrorLocked: stdSetErrorLocked, + decrypt: stdDecrypt, + setTrafficSecret: stdSetTrafficSecret, + explicitNonceLen: stdExplicitNonceLen, + }, true + }) +} + +//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord +func stdTLSReadRecord(c unsafe.Pointer) error + +//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage +func stdTLSHandlePostHandshakeMessage(c unsafe.Pointer) error + +//go:linkname stdWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked +func stdWriteRecordLocked(c unsafe.Pointer, typ uint16, data []byte) (int, error) + +//go:linkname stdSetErrorLocked crypto/tls.(*halfConn).setErrorLocked +func stdSetErrorLocked(hc unsafe.Pointer, err error) error + +//go:linkname stdDecrypt crypto/tls.(*halfConn).decrypt +func stdDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error) + +//go:linkname stdSetTrafficSecret crypto/tls.(*halfConn).setTrafficSecret +func stdSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte) + +//go:linkname stdExplicitNonceLen crypto/tls.(*halfConn).explicitNonceLen +func stdExplicitNonceLen(hc unsafe.Pointer) int diff --git a/common/badtls/registry_utls.go b/common/badtls/registry_utls.go new file mode 100644 index 00000000..c0454355 --- /dev/null +++ b/common/badtls/registry_utls.go @@ -0,0 +1,56 @@ +//go:build go1.25 && !without_badtls + +package badtls + +import ( + "net" + "unsafe" + + N "github.com/sagernet/sing/common/network" + + "github.com/metacubex/utls" +) + +func init() { + methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) { + var pointer unsafe.Pointer + if uConn, loaded := N.CastReader[*tls.Conn](conn); loaded { + pointer = unsafe.Pointer(uConn) + } else if uConn, loaded := N.CastReader[*tls.UConn](conn); loaded { + pointer = unsafe.Pointer(uConn.Conn) + } else { + return nil, nil, false + } + return pointer, &Methods{ + readRecord: utlsReadRecord, + handlePostHandshakeMessage: utlsHandlePostHandshakeMessage, + writeRecordLocked: utlsWriteRecordLocked, + + setErrorLocked: utlsSetErrorLocked, + decrypt: utlsDecrypt, + setTrafficSecret: utlsSetTrafficSecret, + explicitNonceLen: utlsExplicitNonceLen, + }, true + }) +} + +//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord +func utlsReadRecord(c unsafe.Pointer) error + +//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage +func utlsHandlePostHandshakeMessage(c unsafe.Pointer) error + +//go:linkname utlsWriteRecordLocked github.com/metacubex/utls.(*Conn).writeRecordLocked +func utlsWriteRecordLocked(hc unsafe.Pointer, typ uint16, data []byte) (int, error) + +//go:linkname utlsSetErrorLocked github.com/metacubex/utls.(*halfConn).setErrorLocked +func utlsSetErrorLocked(hc unsafe.Pointer, err error) error + +//go:linkname utlsDecrypt github.com/metacubex/utls.(*halfConn).decrypt +func utlsDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error) + +//go:linkname utlsSetTrafficSecret github.com/metacubex/utls.(*halfConn).setTrafficSecret +func utlsSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte) + +//go:linkname utlsExplicitNonceLen github.com/metacubex/utls.(*halfConn).explicitNonceLen +func utlsExplicitNonceLen(hc unsafe.Pointer) int diff --git a/common/badversion/version.go b/common/badversion/version.go index ccff02a6..a8404297 100644 --- a/common/badversion/version.go +++ b/common/badversion/version.go @@ -5,6 +5,8 @@ import ( "strings" F "github.com/sagernet/sing/common/format" + + "golang.org/x/mod/semver" ) type Version struct { @@ -16,7 +18,19 @@ type Version struct { PreReleaseVersion int } -func (v Version) After(anotherVersion Version) bool { +func (v Version) LessThan(anotherVersion Version) bool { + return !v.GreaterThanOrEqual(anotherVersion) +} + +func (v Version) LessThanOrEqual(anotherVersion Version) bool { + return v == anotherVersion || anotherVersion.GreaterThan(v) +} + +func (v Version) GreaterThanOrEqual(anotherVersion Version) bool { + return v == anotherVersion || v.GreaterThan(anotherVersion) +} + +func (v Version) GreaterThan(anotherVersion Version) bool { if v.Major > anotherVersion.Major { return true } else if v.Major < anotherVersion.Major { @@ -44,19 +58,29 @@ func (v Version) After(anotherVersion Version) bool { } else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion { return false } - } else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" { + } + preReleaseIdentifier := parsePreReleaseIdentifier(v.PreReleaseIdentifier) + anotherPreReleaseIdentifier := parsePreReleaseIdentifier(anotherVersion.PreReleaseIdentifier) + if preReleaseIdentifier < anotherPreReleaseIdentifier { 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" { + } else if preReleaseIdentifier > anotherPreReleaseIdentifier { return false } } return false } +func parsePreReleaseIdentifier(identifier string) int { + if strings.HasPrefix(identifier, "rc") { + return 1 + } else if strings.HasPrefix(identifier, "beta") { + return 2 + } else if strings.HasPrefix(identifier, "alpha") { + return 3 + } + return 0 +} + func (v Version) VersionString() string { return F.ToString(v.Major, ".", v.Minor, ".", v.Patch) } @@ -83,6 +107,10 @@ func (v Version) BadString() string { return version } +func IsValid(versionName string) bool { + return semver.IsValid("v" + versionName) +} + func Parse(versionName string) (version Version) { if strings.HasPrefix(versionName, "v") { versionName = versionName[1:] diff --git a/common/badversion/version_test.go b/common/badversion/version_test.go index 9d6e8a7c..d6d5a73c 100644 --- a/common/badversion/version_test.go +++ b/common/badversion/version_test.go @@ -10,9 +10,9 @@ func TestCompareVersion(t *testing.T) { t.Parallel() require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String()) require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString()) - require.True(t, Parse("1.3.0").After(Parse("1.3-beta1"))) - require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1"))) - require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1"))) - require.True(t, Parse("1.3.1").After(Parse("1.3.0"))) - require.True(t, Parse("1.4").After(Parse("1.3"))) + require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3-beta1"))) + require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3.0-beta1"))) + require.True(t, Parse("1.3.0-beta1").GreaterThan(Parse("1.3.0-alpha1"))) + require.True(t, Parse("1.3.1").GreaterThan(Parse("1.3.0"))) + require.True(t, Parse("1.4").GreaterThan(Parse("1.3"))) } diff --git a/common/ktls/ktls.go b/common/ktls/ktls.go new file mode 100644 index 00000000..36f36b25 --- /dev/null +++ b/common/ktls/ktls.go @@ -0,0 +1,106 @@ +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "context" + "crypto/tls" + "io" + "net" + "os" + "syscall" + + "github.com/sagernet/sing-box/common/badtls" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" +) + +type Conn struct { + aTLS.Conn + ctx context.Context + logger logger.ContextLogger + conn net.Conn + rawConn *badtls.RawConn + syscallConn syscall.Conn + rawSyscallConn syscall.RawConn + readWaitOptions N.ReadWaitOptions + kernelTx bool + kernelRx bool +} + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + err := Load() + if err != nil { + return nil, err + } + syscallConn, isSyscallConn := N.CastReader[interface { + io.Reader + syscall.Conn + }](conn.NetConn()) + if !isSyscallConn { + return nil, os.ErrInvalid + } + rawSyscallConn, err := syscallConn.SyscallConn() + if err != nil { + return nil, err + } + rawConn, err := badtls.NewRawConn(conn) + if err != nil { + return nil, err + } + if *rawConn.Vers != tls.VersionTLS13 { + return nil, os.ErrInvalid + } + for rawConn.RawInput.Len() > 0 { + err = rawConn.ReadRecord() + if err != nil { + return nil, err + } + for rawConn.Hand.Len() > 0 { + err = rawConn.HandlePostHandshakeMessage() + if err != nil { + return nil, E.Cause(err, "handle post-handshake messages") + } + } + } + kConn := &Conn{ + Conn: conn, + ctx: ctx, + logger: logger, + conn: conn.NetConn(), + rawConn: rawConn, + syscallConn: syscallConn, + rawSyscallConn: rawSyscallConn, + } + err = kConn.setupKernel(txOffload, rxOffload) + if err != nil { + return nil, err + } + return kConn, nil +} + +func (c *Conn) Upstream() any { + return c.Conn +} + +func (c *Conn) SyscallConnForRead() syscall.Conn { + if !c.kernelRx { + return nil + } + if !*c.rawConn.IsClient { + c.logger.WarnContext(c.ctx, "ktls: RX splice is unavailable on the server size, since it will cause an unknown failure") + return nil + } + c.logger.DebugContext(c.ctx, "ktls: RX splice requested") + return c.syscallConn +} + +func (c *Conn) SyscallConnForWrite() syscall.Conn { + if !c.kernelTx { + return nil + } + c.logger.DebugContext(c.ctx, "ktls: TX splice requested") + return c.syscallConn +} diff --git a/common/ktls/ktls_alert.go b/common/ktls/ktls_alert.go new file mode 100644 index 00000000..8c68d36b --- /dev/null +++ b/common/ktls/ktls_alert.go @@ -0,0 +1,80 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "crypto/tls" + "net" +) + +const ( + // alert level + alertLevelWarning = 1 + alertLevelError = 2 +) + +const ( + alertCloseNotify = 0 + alertUnexpectedMessage = 10 + alertBadRecordMAC = 20 + alertDecryptionFailed = 21 + alertRecordOverflow = 22 + alertDecompressionFailure = 30 + alertHandshakeFailure = 40 + alertBadCertificate = 42 + alertUnsupportedCertificate = 43 + alertCertificateRevoked = 44 + alertCertificateExpired = 45 + alertCertificateUnknown = 46 + alertIllegalParameter = 47 + alertUnknownCA = 48 + alertAccessDenied = 49 + alertDecodeError = 50 + alertDecryptError = 51 + alertExportRestriction = 60 + alertProtocolVersion = 70 + alertInsufficientSecurity = 71 + alertInternalError = 80 + alertInappropriateFallback = 86 + alertUserCanceled = 90 + alertNoRenegotiation = 100 + alertMissingExtension = 109 + alertUnsupportedExtension = 110 + alertCertificateUnobtainable = 111 + alertUnrecognizedName = 112 + alertBadCertificateStatusResponse = 113 + alertBadCertificateHashValue = 114 + alertUnknownPSKIdentity = 115 + alertCertificateRequired = 116 + alertNoApplicationProtocol = 120 + alertECHRequired = 121 +) + +func (c *Conn) sendAlertLocked(err uint8) error { + switch err { + case alertNoRenegotiation, alertCloseNotify: + c.rawConn.Tmp[0] = alertLevelWarning + default: + c.rawConn.Tmp[0] = alertLevelError + } + c.rawConn.Tmp[1] = byte(err) + + _, writeErr := c.writeRecordLocked(recordTypeAlert, c.rawConn.Tmp[0:2]) + if err == alertCloseNotify { + // closeNotify is a special case in that it isn't an error. + return writeErr + } + + return c.rawConn.Out.SetErrorLocked(&net.OpError{Op: "local error", Err: tls.AlertError(err)}) +} + +// sendAlert sends a TLS alert message. +func (c *Conn) sendAlert(err uint8) error { + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + return c.sendAlertLocked(err) +} diff --git a/common/ktls/ktls_cipher_suites_linux.go b/common/ktls/ktls_cipher_suites_linux.go new file mode 100644 index 00000000..348b1c42 --- /dev/null +++ b/common/ktls/ktls_cipher_suites_linux.go @@ -0,0 +1,326 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "crypto/tls" + "unsafe" + + "github.com/sagernet/sing-box/common/badtls" +) + +type kernelCryptoCipherType uint16 + +const ( + TLS_CIPHER_AES_GCM_128 kernelCryptoCipherType = 51 + TLS_CIPHER_AES_GCM_128_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_AES_GCM_128_KEY_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_GCM_128_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_AES_GCM_128_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_AES_GCM_256 kernelCryptoCipherType = 52 + TLS_CIPHER_AES_GCM_256_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_AES_GCM_256_KEY_SIZE kernelCryptoCipherType = 32 + TLS_CIPHER_AES_GCM_256_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_AES_GCM_256_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_AES_CCM_128 kernelCryptoCipherType = 53 + TLS_CIPHER_AES_CCM_128_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_AES_CCM_128_KEY_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_CCM_128_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_AES_CCM_128_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_CHACHA20_POLY1305 kernelCryptoCipherType = 54 + TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE kernelCryptoCipherType = 12 + TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE kernelCryptoCipherType = 32 + TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE kernelCryptoCipherType = 0 + TLS_CIPHER_CHACHA20_POLY1305_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + // TLS_CIPHER_SM4_GCM kernelCryptoCipherType = 55 + // TLS_CIPHER_SM4_GCM_IV_SIZE kernelCryptoCipherType = 8 + // TLS_CIPHER_SM4_GCM_KEY_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_GCM_SALT_SIZE kernelCryptoCipherType = 4 + // TLS_CIPHER_SM4_GCM_TAG_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + // TLS_CIPHER_SM4_CCM kernelCryptoCipherType = 56 + // TLS_CIPHER_SM4_CCM_IV_SIZE kernelCryptoCipherType = 8 + // TLS_CIPHER_SM4_CCM_KEY_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_CCM_SALT_SIZE kernelCryptoCipherType = 4 + // TLS_CIPHER_SM4_CCM_TAG_SIZE kernelCryptoCipherType = 16 + // TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_ARIA_GCM_128 kernelCryptoCipherType = 57 + TLS_CIPHER_ARIA_GCM_128_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_ARIA_GCM_128_KEY_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_ARIA_GCM_128_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_ARIA_GCM_128_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE kernelCryptoCipherType = 8 + + TLS_CIPHER_ARIA_GCM_256 kernelCryptoCipherType = 58 + TLS_CIPHER_ARIA_GCM_256_IV_SIZE kernelCryptoCipherType = 8 + TLS_CIPHER_ARIA_GCM_256_KEY_SIZE kernelCryptoCipherType = 32 + TLS_CIPHER_ARIA_GCM_256_SALT_SIZE kernelCryptoCipherType = 4 + TLS_CIPHER_ARIA_GCM_256_TAG_SIZE kernelCryptoCipherType = 16 + TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE kernelCryptoCipherType = 8 +) + +type kernelCrypto interface { + String() string +} + +type kernelCryptoInfo struct { + version uint16 + cipher_type kernelCryptoCipherType +} + +var _ kernelCrypto = &kernelCryptoAES128GCM{} + +type kernelCryptoAES128GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_AES_GCM_128_IV_SIZE]byte + key [TLS_CIPHER_AES_GCM_128_KEY_SIZE]byte + salt [TLS_CIPHER_AES_GCM_128_SALT_SIZE]byte + rec_seq [TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoAES128GCM) String() string { + crypto.cipher_type = TLS_CIPHER_AES_GCM_128 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoAES256GCM{} + +type kernelCryptoAES256GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_AES_GCM_256_IV_SIZE]byte + key [TLS_CIPHER_AES_GCM_256_KEY_SIZE]byte + salt [TLS_CIPHER_AES_GCM_256_SALT_SIZE]byte + rec_seq [TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoAES256GCM) String() string { + crypto.cipher_type = TLS_CIPHER_AES_GCM_256 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoAES128CCM{} + +type kernelCryptoAES128CCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_AES_CCM_128_IV_SIZE]byte + key [TLS_CIPHER_AES_CCM_128_KEY_SIZE]byte + salt [TLS_CIPHER_AES_CCM_128_SALT_SIZE]byte + rec_seq [TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoAES128CCM) String() string { + crypto.cipher_type = TLS_CIPHER_AES_CCM_128 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoChacha20Poly1035{} + +type kernelCryptoChacha20Poly1035 struct { + kernelCryptoInfo + iv [TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE]byte + key [TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE]byte + salt [TLS_CIPHER_CHACHA20_POLY1305_SALT_SIZE]byte + rec_seq [TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoChacha20Poly1035) String() string { + crypto.cipher_type = TLS_CIPHER_CHACHA20_POLY1305 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +// var _ kernelCrypto = &kernelCryptoSM4GCM{} + +// type kernelCryptoSM4GCM struct { +// kernelCryptoInfo +// iv [TLS_CIPHER_SM4_GCM_IV_SIZE]byte +// key [TLS_CIPHER_SM4_GCM_KEY_SIZE]byte +// salt [TLS_CIPHER_SM4_GCM_SALT_SIZE]byte +// rec_seq [TLS_CIPHER_SM4_GCM_REC_SEQ_SIZE]byte +// } + +// func (crypto *kernelCryptoSM4GCM) String() string { +// crypto.cipher_type = TLS_CIPHER_SM4_GCM +// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +// } + +// var _ kernelCrypto = &kernelCryptoSM4CCM{} + +// type kernelCryptoSM4CCM struct { +// kernelCryptoInfo +// iv [TLS_CIPHER_SM4_CCM_IV_SIZE]byte +// key [TLS_CIPHER_SM4_CCM_KEY_SIZE]byte +// salt [TLS_CIPHER_SM4_CCM_SALT_SIZE]byte +// rec_seq [TLS_CIPHER_SM4_CCM_REC_SEQ_SIZE]byte +// } + +// func (crypto *kernelCryptoSM4CCM) String() string { +// crypto.cipher_type = TLS_CIPHER_SM4_CCM +// return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +// } + +var _ kernelCrypto = &kernelCryptoARIA128GCM{} + +type kernelCryptoARIA128GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_ARIA_GCM_128_IV_SIZE]byte + key [TLS_CIPHER_ARIA_GCM_128_KEY_SIZE]byte + salt [TLS_CIPHER_ARIA_GCM_128_SALT_SIZE]byte + rec_seq [TLS_CIPHER_ARIA_GCM_128_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoARIA128GCM) String() string { + crypto.cipher_type = TLS_CIPHER_ARIA_GCM_128 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +var _ kernelCrypto = &kernelCryptoARIA256GCM{} + +type kernelCryptoARIA256GCM struct { + kernelCryptoInfo + iv [TLS_CIPHER_ARIA_GCM_256_IV_SIZE]byte + key [TLS_CIPHER_ARIA_GCM_256_KEY_SIZE]byte + salt [TLS_CIPHER_ARIA_GCM_256_SALT_SIZE]byte + rec_seq [TLS_CIPHER_ARIA_GCM_256_REC_SEQ_SIZE]byte +} + +func (crypto *kernelCryptoARIA256GCM) String() string { + crypto.cipher_type = TLS_CIPHER_ARIA_GCM_256 + return string((*[unsafe.Sizeof(*crypto)]byte)(unsafe.Pointer(crypto))[:]) +} + +func kernelCipher(kernel *Support, hc *badtls.RawHalfConn, cipherSuite uint16, isRX bool) kernelCrypto { + if !kernel.TLS { + return nil + } + + switch *hc.Version { + case tls.VersionTLS12: + if isRX && !kernel.TLS_Version13_RX { + return nil + } + + case tls.VersionTLS13: + if !kernel.TLS_Version13 { + return nil + } + + if isRX && !kernel.TLS_Version13_RX { + return nil + } + + default: + return nil + } + + var key, iv []byte + if *hc.Version == tls.VersionTLS13 { + key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), *hc.TrafficSecret) + /*if isRX { + key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.RemoteTrafficSecret) + } else { + key, iv = trafficKey(cipherSuiteTLS13ByID(cipherSuite), keyLog.TrafficSecret) + }*/ + } else { + // csPtr := cipherSuiteByID(cipherSuite) + // keysFromMasterSecret(*hc.Version, csPtr, keyLog.Secret, keyLog.Random) + return nil + } + + switch cipherSuite { + case tls.TLS_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + crypto := new(kernelCryptoAES128GCM) + + crypto.version = *hc.Version + copy(crypto.key[:], key) + copy(crypto.iv[:], iv[4:]) + copy(crypto.salt[:], iv[:4]) + crypto.rec_seq = *hc.Seq + + return crypto + case tls.TLS_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + if !kernel.TLS_AES_256_GCM { + return nil + } + + crypto := new(kernelCryptoAES256GCM) + + crypto.version = *hc.Version + copy(crypto.key[:], key) + copy(crypto.iv[:], iv[4:]) + copy(crypto.salt[:], iv[:4]) + crypto.rec_seq = *hc.Seq + + return crypto + //case tls.TLS_AES_128_CCM_SHA256, tls.TLS_RSA_WITH_AES_128_CCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_SHA256: + // if !kernel.TLS_AES_128_CCM { + // return nil + // } + // + // crypto := new(kernelCryptoAES128CCM) + // + // crypto.version = *hc.Version + // copy(crypto.key[:], key) + // copy(crypto.iv[:], iv[4:]) + // copy(crypto.salt[:], iv[:4]) + // crypto.rec_seq = *hc.Seq + // + // return crypto + case tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + if !kernel.TLS_CHACHA20_POLY1305 { + return nil + } + + crypto := new(kernelCryptoChacha20Poly1035) + + crypto.version = *hc.Version + copy(crypto.key[:], key) + copy(crypto.iv[:], iv) + crypto.rec_seq = *hc.Seq + + return crypto + //case tls.TLS_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256: + // if !kernel.TLS_ARIA_GCM { + // return nil + // } + // + // crypto := new(kernelCryptoARIA128GCM) + // + // crypto.version = *hc.Version + // copy(crypto.key[:], key) + // copy(crypto.iv[:], iv[4:]) + // copy(crypto.salt[:], iv[:4]) + // crypto.rec_seq = *hc.Seq + // + // return crypto + //case tls.TLS_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384: + // if !kernel.TLS_ARIA_GCM { + // return nil + // } + // + // crypto := new(kernelCryptoARIA256GCM) + // + // crypto.version = *hc.Version + // copy(crypto.key[:], key) + // copy(crypto.iv[:], iv[4:]) + // copy(crypto.salt[:], iv[:4]) + // crypto.rec_seq = *hc.Seq + // + // return crypto + default: + return nil + } +} diff --git a/common/ktls/ktls_close.go b/common/ktls/ktls_close.go new file mode 100644 index 00000000..f9392a56 --- /dev/null +++ b/common/ktls/ktls_close.go @@ -0,0 +1,67 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "fmt" + "net" + "time" +) + +func (c *Conn) Close() error { + if !c.kernelTx { + return c.Conn.Close() + } + + // Interlock with Conn.Write above. + var x int32 + for { + x = c.rawConn.ActiveCall.Load() + if x&1 != 0 { + return net.ErrClosed + } + if c.rawConn.ActiveCall.CompareAndSwap(x, x|1) { + break + } + } + if x != 0 { + // io.Writer and io.Closer should not be used concurrently. + // If Close is called while a Write is currently in-flight, + // interpret that as a sign that this Close is really just + // being used to break the Write and/or clean up resources and + // avoid sending the alertCloseNotify, which may block + // waiting on handshakeMutex or the c.out mutex. + return c.conn.Close() + } + + var alertErr error + if c.rawConn.IsHandshakeComplete.Load() { + if err := c.closeNotify(); err != nil { + alertErr = fmt.Errorf("tls: failed to send closeNotify alert (but connection was closed anyway): %w", err) + } + } + + if err := c.conn.Close(); err != nil { + return err + } + return alertErr +} + +func (c *Conn) closeNotify() error { + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + + if !*c.rawConn.CloseNotifySent { + // Set a Write Deadline to prevent possibly blocking forever. + c.SetWriteDeadline(time.Now().Add(time.Second * 5)) + *c.rawConn.CloseNotifyErr = c.sendAlertLocked(alertCloseNotify) + *c.rawConn.CloseNotifySent = true + // Any subsequent writes will fail. + c.SetWriteDeadline(time.Now()) + } + return *c.rawConn.CloseNotifyErr +} diff --git a/common/ktls/ktls_const.go b/common/ktls/ktls_const.go new file mode 100644 index 00000000..0b8e72e8 --- /dev/null +++ b/common/ktls/ktls_const.go @@ -0,0 +1,24 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +const ( + maxPlaintext = 16384 // maximum plaintext payload length + maxCiphertext = 16384 + 2048 // maximum ciphertext payload length + maxCiphertextTLS13 = 16384 + 256 // maximum ciphertext length in TLS 1.3 + recordHeaderLen = 5 // record header length + maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB) + maxHandshakeCertificateMsg = 262144 // maximum certificate message size (256 KiB) + maxUselessRecords = 16 // maximum number of consecutive non-advancing records +) + +const ( + recordTypeChangeCipherSpec = 20 + recordTypeAlert = 21 + recordTypeHandshake = 22 + recordTypeApplicationData = 23 +) diff --git a/common/ktls/ktls_handshake_messages.go b/common/ktls/ktls_handshake_messages.go new file mode 100644 index 00000000..e80531e1 --- /dev/null +++ b/common/ktls/ktls_handshake_messages.go @@ -0,0 +1,238 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "fmt" + + "golang.org/x/crypto/cryptobyte" +) + +// The marshalingFunction type is an adapter to allow the use of ordinary +// functions as cryptobyte.MarshalingValue. +type marshalingFunction func(b *cryptobyte.Builder) error + +func (f marshalingFunction) Marshal(b *cryptobyte.Builder) error { + return f(b) +} + +// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If +// the length of the sequence is not the value specified, it produces an error. +func addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) { + b.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error { + if len(v) != n { + return fmt.Errorf("invalid value length: expected %d, got %d", n, len(v)) + } + b.AddBytes(v) + return nil + })) +} + +// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder. +func addUint64(b *cryptobyte.Builder, v uint64) { + b.AddUint32(uint32(v >> 32)) + b.AddUint32(uint32(v)) +} + +// readUint64 decodes a big-endian, 64-bit value into out and advances over it. +// It reports whether the read was successful. +func readUint64(s *cryptobyte.String, out *uint64) bool { + var hi, lo uint32 + if !s.ReadUint32(&hi) || !s.ReadUint32(&lo) { + return false + } + *out = uint64(hi)<<32 | uint64(lo) + return true +} + +// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a +// []byte instead of a cryptobyte.String. +func readUint8LengthPrefixed(s *cryptobyte.String, out *[]byte) bool { + return s.ReadUint8LengthPrefixed((*cryptobyte.String)(out)) +} + +// readUint16LengthPrefixed acts like s.ReadUint16LengthPrefixed, but targets a +// []byte instead of a cryptobyte.String. +func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool { + return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out)) +} + +// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a +// []byte instead of a cryptobyte.String. +func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool { + return s.ReadUint24LengthPrefixed((*cryptobyte.String)(out)) +} + +type keyUpdateMsg struct { + updateRequested bool +} + +func (m *keyUpdateMsg) marshal() ([]byte, error) { + var b cryptobyte.Builder + b.AddUint8(typeKeyUpdate) + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + if m.updateRequested { + b.AddUint8(1) + } else { + b.AddUint8(0) + } + }) + + return b.Bytes() +} + +func (m *keyUpdateMsg) unmarshal(data []byte) bool { + s := cryptobyte.String(data) + + var updateRequested uint8 + if !s.Skip(4) || // message type and uint24 length field + !s.ReadUint8(&updateRequested) || !s.Empty() { + return false + } + switch updateRequested { + case 0: + m.updateRequested = false + case 1: + m.updateRequested = true + default: + return false + } + return true +} + +// TLS handshake message types. +const ( + typeHelloRequest uint8 = 0 + typeClientHello uint8 = 1 + typeServerHello uint8 = 2 + typeNewSessionTicket uint8 = 4 + typeEndOfEarlyData uint8 = 5 + typeEncryptedExtensions uint8 = 8 + typeCertificate uint8 = 11 + typeServerKeyExchange uint8 = 12 + typeCertificateRequest uint8 = 13 + typeServerHelloDone uint8 = 14 + typeCertificateVerify uint8 = 15 + typeClientKeyExchange uint8 = 16 + typeFinished uint8 = 20 + typeCertificateStatus uint8 = 22 + typeKeyUpdate uint8 = 24 + typeCompressedCertificate uint8 = 25 + typeMessageHash uint8 = 254 // synthetic message +) + +// TLS compression types. +const ( + compressionNone uint8 = 0 +) + +// TLS extension numbers +const ( + extensionServerName uint16 = 0 + extensionStatusRequest uint16 = 5 + extensionSupportedCurves uint16 = 10 // supported_groups in TLS 1.3, see RFC 8446, Section 4.2.7 + extensionSupportedPoints uint16 = 11 + extensionSignatureAlgorithms uint16 = 13 + extensionALPN uint16 = 16 + extensionSCT uint16 = 18 + extensionPadding uint16 = 21 + extensionExtendedMasterSecret uint16 = 23 + extensionCompressCertificate uint16 = 27 // compress_certificate in TLS 1.3 + extensionSessionTicket uint16 = 35 + extensionPreSharedKey uint16 = 41 + extensionEarlyData uint16 = 42 + extensionSupportedVersions uint16 = 43 + extensionCookie uint16 = 44 + extensionPSKModes uint16 = 45 + extensionCertificateAuthorities uint16 = 47 + extensionSignatureAlgorithmsCert uint16 = 50 + extensionKeyShare uint16 = 51 + extensionQUICTransportParameters uint16 = 57 + extensionALPS uint16 = 17513 + extensionRenegotiationInfo uint16 = 0xff01 + extensionECHOuterExtensions uint16 = 0xfd00 + extensionEncryptedClientHello uint16 = 0xfe0d +) + +type handshakeMessage interface { + marshal() ([]byte, error) + unmarshal([]byte) bool +} +type newSessionTicketMsgTLS13 struct { + lifetime uint32 + ageAdd uint32 + nonce []byte + label []byte + maxEarlyData uint32 +} + +func (m *newSessionTicketMsgTLS13) marshal() ([]byte, error) { + var b cryptobyte.Builder + b.AddUint8(typeNewSessionTicket) + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint32(m.lifetime) + b.AddUint32(m.ageAdd) + b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.nonce) + }) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(m.label) + }) + + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + if m.maxEarlyData > 0 { + b.AddUint16(extensionEarlyData) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint32(m.maxEarlyData) + }) + } + }) + }) + + return b.Bytes() +} + +func (m *newSessionTicketMsgTLS13) unmarshal(data []byte) bool { + *m = newSessionTicketMsgTLS13{} + s := cryptobyte.String(data) + + var extensions cryptobyte.String + if !s.Skip(4) || // message type and uint24 length field + !s.ReadUint32(&m.lifetime) || + !s.ReadUint32(&m.ageAdd) || + !readUint8LengthPrefixed(&s, &m.nonce) || + !readUint16LengthPrefixed(&s, &m.label) || + !s.ReadUint16LengthPrefixed(&extensions) || + !s.Empty() { + return false + } + + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + return false + } + + switch extension { + case extensionEarlyData: + if !extData.ReadUint32(&m.maxEarlyData) { + return false + } + default: + // Ignore unknown extensions. + continue + } + + if !extData.Empty() { + return false + } + } + + return true +} diff --git a/common/ktls/ktls_key_update.go b/common/ktls/ktls_key_update.go new file mode 100644 index 00000000..9e0d0ee1 --- /dev/null +++ b/common/ktls/ktls_key_update.go @@ -0,0 +1,173 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "os" +) + +// handlePostHandshakeMessage processes a handshake message arrived after the +// handshake is complete. Up to TLS 1.2, it indicates the start of a renegotiation. +func (c *Conn) handlePostHandshakeMessage() error { + if *c.rawConn.Vers != tls.VersionTLS13 { + return errors.New("ktls: kernel does not support TLS 1.2 renegotiation") + } + + msg, err := c.readHandshake(nil) + if err != nil { + return err + } + //c.retryCount++ + //if c.retryCount > maxUselessRecords { + // c.sendAlert(alertUnexpectedMessage) + // return c.in.setErrorLocked(errors.New("tls: too many non-advancing records")) + //} + + switch msg := msg.(type) { + case *newSessionTicketMsgTLS13: + // return errors.New("ktls: received new session ticket") + return nil + case *keyUpdateMsg: + return c.handleKeyUpdate(msg) + } + // The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest + // as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an + // unexpected_message alert here doesn't provide it with enough information to distinguish + // this condition from other unexpected messages. This is probably fine. + c.sendAlert(alertUnexpectedMessage) + return fmt.Errorf("tls: received unexpected handshake message of type %T", msg) +} + +func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error { + //if c.quic != nil { + // c.sendAlert(alertUnexpectedMessage) + // return c.in.setErrorLocked(errors.New("tls: received unexpected key update message")) + //} + + cipherSuite := cipherSuiteTLS13ByID(*c.rawConn.CipherSuite) + if cipherSuite == nil { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertInternalError)) + } + + newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.In.TrafficSecret) + c.rawConn.In.SetTrafficSecret(cipherSuite, 0 /*tls.QUICEncryptionLevelInitial*/, newSecret) + + err := c.resetupRX() + if err != nil { + c.sendAlert(alertInternalError) + return c.rawConn.In.SetErrorLocked(fmt.Errorf("ktls: resetupRX failed: %w", err)) + } + + if keyUpdate.updateRequested { + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + + resetup, err := c.resetupTX() + if err != nil { + c.sendAlertLocked(alertInternalError) + return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err)) + } + + msg := &keyUpdateMsg{} + msgBytes, err := msg.marshal() + if err != nil { + return err + } + _, err = c.writeRecordLocked(recordTypeHandshake, msgBytes) + if err != nil { + // Surface the error at the next write. + c.rawConn.Out.SetErrorLocked(err) + return nil + } + + newSecret := nextTrafficSecret(cipherSuite, *c.rawConn.Out.TrafficSecret) + c.rawConn.Out.SetTrafficSecret(cipherSuite, 0 /*QUICEncryptionLevelInitial*/, newSecret) + + err = resetup() + if err != nil { + return c.rawConn.Out.SetErrorLocked(fmt.Errorf("ktls: resetupTX failed: %w", err)) + } + } + + return nil +} + +func (c *Conn) readHandshakeBytes(n int) error { + //if c.quic != nil { + // return c.quicReadHandshakeBytes(n) + //} + for c.rawConn.Hand.Len() < n { + if err := c.readRecord(); err != nil { + return err + } + } + return nil +} + +func (c *Conn) readHandshake(transcript io.Writer) (any, error) { + if err := c.readHandshakeBytes(4); err != nil { + return nil, err + } + data := c.rawConn.Hand.Bytes() + + maxHandshakeSize := maxHandshake + // hasVers indicates we're past the first message, forcing someone trying to + // make us just allocate a large buffer to at least do the initial part of + // the handshake first. + //if c.haveVers && data[0] == typeCertificate { + // Since certificate messages are likely to be the only messages that + // can be larger than maxHandshake, we use a special limit for just + // those messages. + //maxHandshakeSize = maxHandshakeCertificateMsg + //} + + n := int(data[1])<<16 | int(data[2])<<8 | int(data[3]) + if n > maxHandshakeSize { + c.sendAlertLocked(alertInternalError) + return nil, c.rawConn.In.SetErrorLocked(fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshakeSize)) + } + if err := c.readHandshakeBytes(4 + n); err != nil { + return nil, err + } + data = c.rawConn.Hand.Next(4 + n) + return c.unmarshalHandshakeMessage(data, transcript) +} + +func (c *Conn) unmarshalHandshakeMessage(data []byte, transcript io.Writer) (any, error) { + var m handshakeMessage + switch data[0] { + case typeNewSessionTicket: + if *c.rawConn.Vers == tls.VersionTLS13 { + m = new(newSessionTicketMsgTLS13) + } else { + return nil, os.ErrInvalid + } + case typeKeyUpdate: + m = new(keyUpdateMsg) + default: + return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + // The handshake message unmarshalers + // expect to be able to keep references to data, + // so pass in a fresh copy that won't be overwritten. + data = append([]byte(nil), data...) + + if !m.unmarshal(data) { + return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError)) + } + + if transcript != nil { + transcript.Write(data) + } + + return m, nil +} diff --git a/common/ktls/ktls_linux.go b/common/ktls/ktls_linux.go new file mode 100644 index 00000000..313fe381 --- /dev/null +++ b/common/ktls/ktls_linux.go @@ -0,0 +1,329 @@ +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "crypto/tls" + "errors" + "io" + "os" + "strings" + "sync" + "syscall" + "unsafe" + + "github.com/sagernet/sing-box/common/badversion" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/shell" + + "golang.org/x/sys/unix" +) + +// mod from https://gitlab.com/go-extension/tls + +const ( + TLS_TX = 1 + TLS_RX = 2 + TLS_TX_ZEROCOPY_RO = 3 // TX zerocopy (only sendfile now) + TLS_RX_EXPECT_NO_PAD = 4 // Attempt opportunistic zero-copy, TLS 1.3 only + + TLS_SET_RECORD_TYPE = 1 + TLS_GET_RECORD_TYPE = 2 +) + +type Support struct { + TLS, TLS_RX bool + TLS_Version13, TLS_Version13_RX bool + + TLS_TX_ZEROCOPY bool + TLS_RX_NOPADDING bool + + TLS_AES_256_GCM bool + TLS_AES_128_CCM bool + TLS_CHACHA20_POLY1305 bool + TLS_SM4 bool + TLS_ARIA_GCM bool + + TLS_Version13_KeyUpdate bool +} + +var KernelSupport = sync.OnceValues(func() (*Support, error) { + var uname unix.Utsname + err := unix.Uname(&uname) + if err != nil { + return nil, err + } + + kernelVersion := badversion.Parse(strings.Trim(string(uname.Release[:]), "\x00")) + if err != nil { + return nil, err + } + var support Support + switch { + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 14}): + support.TLS_Version13_KeyUpdate = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6, Minor: 1}): + support.TLS_ARIA_GCM = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 6}): + support.TLS_Version13_RX = true + support.TLS_RX_NOPADDING = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 19}): + support.TLS_TX_ZEROCOPY = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 16}): + support.TLS_SM4 = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 11}): + support.TLS_CHACHA20_POLY1305 = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 2}): + support.TLS_AES_128_CCM = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 5, Minor: 1}): + support.TLS_AES_256_GCM = true + support.TLS_Version13 = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 17}): + support.TLS_RX = true + fallthrough + case kernelVersion.GreaterThanOrEqual(badversion.Version{Major: 4, Minor: 13}): + support.TLS = true + } + + if support.TLS && support.TLS_Version13 { + _, err := os.Stat("/sys/module/tls") + if err != nil { + if os.Getuid() == 0 { + output, err := shell.Exec("modprobe", "tls").Read() + if err != nil { + return nil, E.Extend(E.Cause(err, "modprobe tls"), output) + } + } else { + return nil, E.New("ktls: kernel TLS module not loaded") + } + } + } + + return &support, nil +}) + +func Load() error { + support, err := KernelSupport() + if err != nil { + return E.Cause(err, "ktls: check availability") + } + if !support.TLS || !support.TLS_Version13 { + return E.New("ktls: kernel does not support TLS 1.3") + } + return nil +} + +func (c *Conn) setupKernel(txOffload, rxOffload bool) error { + if !txOffload && !rxOffload { + return os.ErrInvalid + } + support, err := KernelSupport() + if err != nil { + return E.Cause(err, "check availability") + } + if !support.TLS || !support.TLS_Version13 { + return E.New("kernel does not support TLS 1.3") + } + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TCP, unix.TCP_ULP, "tls") + }) + if err != nil { + return os.NewSyscallError("setsockopt", err) + } + + if txOffload { + txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false) + if txCrypto == nil { + return E.New("unsupported cipher suite") + } + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String()) + }) + if err != nil { + return err + } + if support.TLS_TX_ZEROCOPY { + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_TX_ZEROCOPY_RO, 1) + }) + if err != nil { + return err + } + } + c.kernelTx = true + c.logger.DebugContext(c.ctx, "ktls: kernel TLS TX enabled") + } + + if rxOffload { + rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true) + if rxCrypto == nil { + return E.New("unsupported cipher suite") + } + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String()) + }) + if err != nil { + return err + } + if *c.rawConn.Vers >= tls.VersionTLS13 && support.TLS_RX_NOPADDING { + err = control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptInt(int(fd), unix.SOL_TLS, TLS_RX_EXPECT_NO_PAD, 1) + }) + if err != nil { + return err + } + } + c.kernelRx = true + c.logger.DebugContext(c.ctx, "ktls: kernel TLS RX enabled") + } + return nil +} + +func (c *Conn) resetupTX() (func() error, error) { + if !c.kernelTx { + return nil, nil + } + support, err := KernelSupport() + if err != nil { + return nil, err + } + if !support.TLS_Version13_KeyUpdate { + return nil, errors.New("ktls: kernel does not support rekey") + } + txCrypto := kernelCipher(support, c.rawConn.Out, *c.rawConn.CipherSuite, false) + if txCrypto == nil { + return nil, errors.New("ktls: set kernelCipher on unsupported tls session") + } + return func() error { + return control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_TX, txCrypto.String()) + }) + }, nil +} + +func (c *Conn) resetupRX() error { + if !c.kernelRx { + return nil + } + support, err := KernelSupport() + if err != nil { + return err + } + if !support.TLS_Version13_KeyUpdate { + return errors.New("ktls: kernel does not support rekey") + } + rxCrypto := kernelCipher(support, c.rawConn.In, *c.rawConn.CipherSuite, true) + if rxCrypto == nil { + return errors.New("ktls: set kernelCipher on unsupported tls session") + } + return control.Raw(c.rawSyscallConn, func(fd uintptr) error { + return syscall.SetsockoptString(int(fd), unix.SOL_TLS, TLS_RX, rxCrypto.String()) + }) +} + +func (c *Conn) readKernelRecord() (uint8, []byte, error) { + if c.rawConn.RawInput.Len() < maxPlaintext { + c.rawConn.RawInput.Grow(maxPlaintext - c.rawConn.RawInput.Len()) + } + + data := c.rawConn.RawInput.Bytes()[:maxPlaintext] + + // cmsg for record type + buffer := make([]byte, unix.CmsgSpace(1)) + cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0])) + cmsg.SetLen(unix.CmsgLen(1)) + + var iov unix.Iovec + iov.Base = &data[0] + iov.SetLen(len(data)) + + var msg unix.Msghdr + msg.Control = &buffer[0] + msg.Controllen = cmsg.Len + msg.Iov = &iov + msg.Iovlen = 1 + + var n int + var err error + er := c.rawSyscallConn.Read(func(fd uintptr) bool { + n, err = recvmsg(int(fd), &msg, 0) + return err != unix.EAGAIN + }) + if er != nil { + return 0, nil, er + } + switch err { + case nil: + case syscall.EINVAL: + return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertProtocolVersion)) + case syscall.EMSGSIZE: + return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow)) + case syscall.EBADMSG: + return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecryptError)) + default: + return 0, nil, err + } + + if n <= 0 { + return 0, nil, io.EOF + } + + if cmsg.Level == unix.SOL_TLS && cmsg.Type == TLS_GET_RECORD_TYPE { + typ := buffer[unix.CmsgLen(0)] + return typ, data[:n], nil + } + + return recordTypeApplicationData, data[:n], nil +} + +func (c *Conn) writeKernelRecord(typ uint16, data []byte) (int, error) { + if typ == recordTypeApplicationData { + return c.conn.Write(data) + } + + // cmsg for record type + buffer := make([]byte, unix.CmsgSpace(1)) + cmsg := (*unix.Cmsghdr)(unsafe.Pointer(&buffer[0])) + cmsg.SetLen(unix.CmsgLen(1)) + buffer[unix.CmsgLen(0)] = byte(typ) + cmsg.Level = unix.SOL_TLS + cmsg.Type = TLS_SET_RECORD_TYPE + + var iov unix.Iovec + iov.Base = &data[0] + iov.SetLen(len(data)) + + var msg unix.Msghdr + msg.Control = &buffer[0] + msg.Controllen = cmsg.Len + msg.Iov = &iov + msg.Iovlen = 1 + + var n int + var err error + ew := c.rawSyscallConn.Write(func(fd uintptr) bool { + n, err = sendmsg(int(fd), &msg, 0) + return err != unix.EAGAIN + }) + if ew != nil { + return 0, ew + } + return n, err +} + +//go:linkname recvmsg golang.org/x/sys/unix.recvmsg +func recvmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error) + +//go:linkname sendmsg golang.org/x/sys/unix.sendmsg +func sendmsg(fd int, msg *unix.Msghdr, flags int) (n int, err error) diff --git a/common/ktls/ktls_prf.go b/common/ktls/ktls_prf.go new file mode 100644 index 00000000..f74a4876 --- /dev/null +++ b/common/ktls/ktls_prf.go @@ -0,0 +1,24 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import "unsafe" + +//go:linkname cipherSuiteByID github.com/metacubex/utls.cipherSuiteByID +func cipherSuiteByID(id uint16) unsafe.Pointer + +//go:linkname keysFromMasterSecret github.com/metacubex/utls.keysFromMasterSecret +func keysFromMasterSecret(version uint16, suite unsafe.Pointer, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) + +//go:linkname cipherSuiteTLS13ByID github.com/metacubex/utls.cipherSuiteTLS13ByID +func cipherSuiteTLS13ByID(id uint16) unsafe.Pointer + +//go:linkname nextTrafficSecret github.com/metacubex/utls.(*cipherSuiteTLS13).nextTrafficSecret +func nextTrafficSecret(cs unsafe.Pointer, trafficSecret []byte) []byte + +//go:linkname trafficKey github.com/metacubex/utls.(*cipherSuiteTLS13).trafficKey +func trafficKey(cs unsafe.Pointer, trafficSecret []byte) (key, iv []byte) diff --git a/common/ktls/ktls_read.go b/common/ktls/ktls_read.go new file mode 100644 index 00000000..45350441 --- /dev/null +++ b/common/ktls/ktls_read.go @@ -0,0 +1,292 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "net" +) + +func (c *Conn) Read(b []byte) (int, error) { + if !c.kernelRx { + return c.Conn.Read(b) + } + + if len(b) == 0 { + // Put this after Handshake, in case people were calling + // Read(nil) for the side effect of the Handshake. + return 0, nil + } + + c.rawConn.In.Lock() + defer c.rawConn.In.Unlock() + + for c.rawConn.Input.Len() == 0 { + if err := c.readRecord(); err != nil { + return 0, err + } + for c.rawConn.Hand.Len() > 0 { + if err := c.handlePostHandshakeMessage(); err != nil { + return 0, err + } + } + } + + n, _ := c.rawConn.Input.Read(b) + + // If a close-notify alert is waiting, read it so that we can return (n, + // EOF) instead of (n, nil), to signal to the HTTP response reading + // goroutine that the connection is now closed. This eliminates a race + // where the HTTP response reading goroutine would otherwise not observe + // the EOF until its next read, by which time a client goroutine might + // have already tried to reuse the HTTP connection for a new request. + // See https://golang.org/cl/76400046 and https://golang.org/issue/3514 + if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.RawInput.Len() > 0 && + c.rawConn.RawInput.Bytes()[0] == recordTypeAlert { + if err := c.readRecord(); err != nil { + return n, err // will be io.EOF on closeNotify + } + } + + return n, nil +} + +func (c *Conn) readRecord() error { + if *c.rawConn.In.Err != nil { + return *c.rawConn.In.Err + } + + typ, data, err := c.readRawRecord() + if err != nil { + return err + } + + if len(data) > maxPlaintext { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow)) + } + + // Application Data messages are always protected. + if c.rawConn.In.Cipher == nil && typ == recordTypeApplicationData { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + //if typ != recordTypeAlert && typ != recordTypeChangeCipherSpec && len(data) > 0 { + // This is a state-advancing message: reset the retry count. + // c.retryCount = 0 + //} + + // Handshake messages MUST NOT be interleaved with other record types in TLS 1.3. + if *c.rawConn.Vers == tls.VersionTLS13 && typ != recordTypeHandshake && c.rawConn.Hand.Len() > 0 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + switch typ { + default: + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + case recordTypeAlert: + //if c.quic != nil { + // return c.rawConn.In.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) + //} + if len(data) != 2 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + if data[1] == alertCloseNotify { + return c.rawConn.In.SetErrorLocked(io.EOF) + } + if *c.rawConn.Vers == tls.VersionTLS13 { + // TLS 1.3 removed warning-level alerts except for alertUserCanceled + // (RFC 8446, § 6.1). Since at least one major implementation + // (https://bugs.openjdk.org/browse/JDK-8323517) misuses this alert, + // many TLS stacks now ignore it outright when seen in a TLS 1.3 + // handshake (e.g. BoringSSL, NSS, Rustls). + if data[1] == alertUserCanceled { + // Like TLS 1.2 alertLevelWarning alerts, we drop the record and retry. + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + } + return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])}) + } + switch data[0] { + case alertLevelWarning: + // Drop the record on the floor and retry. + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + case alertLevelError: + return c.rawConn.In.SetErrorLocked(&net.OpError{Op: "remote error", Err: tls.AlertError(data[1])}) + default: + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + + case recordTypeChangeCipherSpec: + if len(data) != 1 || data[0] != 1 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertDecodeError)) + } + // Handshake messages are not allowed to fragment across the CCS. + if c.rawConn.Hand.Len() > 0 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + // In TLS 1.3, change_cipher_spec records are ignored until the + // Finished. See RFC 8446, Appendix D.4. Note that according to Section + // 5, a server can send a ChangeCipherSpec before its ServerHello, when + // c.vers is still unset. That's not useful though and suspicious if the + // server then selects a lower protocol version, so don't allow that. + if *c.rawConn.Vers == tls.VersionTLS13 { + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + } + // if !expectChangeCipherSpec { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + //} + //if err := c.rawConn.In.changeCipherSpec(); err != nil { + // return c.rawConn.In.setErrorLocked(c.sendAlert(err.(alert))) + //} + + case recordTypeApplicationData: + // Some OpenSSL servers send empty records in order to randomize the + // CBC RawIV. Ignore a limited number of empty records. + if len(data) == 0 { + return c.retryReadRecord( /*expectChangeCipherSpec*/ ) + } + // Note that data is owned by c.rawInput, following the Next call above, + // to avoid copying the plaintext. This is safe because c.rawInput is + // not read from or written to until c.input is drained. + c.rawConn.Input.Reset(data) + case recordTypeHandshake: + if len(data) == 0 { + return c.rawConn.In.SetErrorLocked(c.sendAlert(alertUnexpectedMessage)) + } + c.rawConn.Hand.Write(data) + } + + return nil +} + +//nolint:staticcheck +func (c *Conn) readRawRecord() (typ uint8, data []byte, err error) { + // Read from kernel. + if c.kernelRx { + return c.readKernelRecord() + } + + // Read header, payload. + if err = c.readFromUntil(c.conn, recordHeaderLen); err != nil { + // RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify + // is an error, but popular web sites seem to do this, so we accept it + // if and only if at the record boundary. + if err == io.ErrUnexpectedEOF && c.rawConn.RawInput.Len() == 0 { + err = io.EOF + } + if e, ok := err.(net.Error); !ok || !e.Temporary() { + c.rawConn.In.SetErrorLocked(err) + } + return + } + hdr := c.rawConn.RawInput.Bytes()[:recordHeaderLen] + typ = hdr[0] + + vers := uint16(hdr[1])<<8 | uint16(hdr[2]) + expectedVers := *c.rawConn.Vers + if expectedVers == tls.VersionTLS13 { + // All TLS 1.3 records are expected to have 0x0303 (1.2) after + // the initial hello (RFC 8446 Section 5.1). + expectedVers = tls.VersionTLS12 + } + n := int(hdr[3])<<8 | int(hdr[4]) + if /*c.haveVers && */ vers != expectedVers { + c.sendAlert(alertProtocolVersion) + msg := fmt.Sprintf("received record with version %x when expecting version %x", vers, expectedVers) + err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg)) + return + } + //if !c.haveVers { + // // First message, be extra suspicious: this might not be a TLS + // // client. Bail out before reading a full 'body', if possible. + // // The current max version is 3.3 so if the version is >= 16.0, + // // it's probably not real. + // if (typ != recordTypeAlert && typ != recordTypeHandshake) || vers >= 0x1000 { + // err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(c.conn, "first record does not look like a TLS handshake")) + // return + // } + //} + if *c.rawConn.Vers == tls.VersionTLS13 && n > maxCiphertextTLS13 || n > maxCiphertext { + c.sendAlert(alertRecordOverflow) + msg := fmt.Sprintf("oversized record received with length %d", n) + err = c.rawConn.In.SetErrorLocked(c.newRecordHeaderError(nil, msg)) + return + } + if err = c.readFromUntil(c.conn, recordHeaderLen+n); err != nil { + if e, ok := err.(net.Error); !ok || !e.Temporary() { + c.rawConn.In.SetErrorLocked(err) + } + return + } + + // Process message. + record := c.rawConn.RawInput.Next(recordHeaderLen + n) + data, typ, err = c.rawConn.In.Decrypt(record) + if err != nil { + err = c.rawConn.In.SetErrorLocked(c.sendAlert(uint8(err.(tls.AlertError)))) + return + } + return +} + +// retryReadRecord recurs into readRecordOrCCS to drop a non-advancing record, like +// a warning alert, empty application_data, or a change_cipher_spec in TLS 1.3. +func (c *Conn) retryReadRecord( /*expectChangeCipherSpec bool*/ ) error { + //c.retryCount++ + //if c.retryCount > maxUselessRecords { + // c.sendAlert(alertUnexpectedMessage) + // return c.in.setErrorLocked(errors.New("tls: too many ignored records")) + //} + return c.readRecord( /*expectChangeCipherSpec*/ ) +} + +// atLeastReader reads from R, stopping with EOF once at least N bytes have been +// read. It is different from an io.LimitedReader in that it doesn't cut short +// the last Read call, and in that it considers an early EOF an error. +type atLeastReader struct { + R io.Reader + N int64 +} + +func (r *atLeastReader) Read(p []byte) (int, error) { + if r.N <= 0 { + return 0, io.EOF + } + n, err := r.R.Read(p) + r.N -= int64(n) // won't underflow unless len(p) >= n > 9223372036854775809 + if r.N > 0 && err == io.EOF { + return n, io.ErrUnexpectedEOF + } + if r.N <= 0 && err == nil { + return n, io.EOF + } + return n, err +} + +// readFromUntil reads from r into c.rawConn.RawInput until c.rawConn.RawInput contains +// at least n bytes or else returns an error. +func (c *Conn) readFromUntil(r io.Reader, n int) error { + if c.rawConn.RawInput.Len() >= n { + return nil + } + needs := n - c.rawConn.RawInput.Len() + // There might be extra input waiting on the wire. Make a best effort + // attempt to fetch it so that it can be used in (*Conn).Read to + // "predict" closeNotify alerts. + c.rawConn.RawInput.Grow(needs + bytes.MinRead) + _, err := c.rawConn.RawInput.ReadFrom(&atLeastReader{r, int64(needs)}) + return err +} + +func (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err tls.RecordHeaderError) { + err.Msg = msg + err.Conn = conn + copy(err.RecordHeader[:], c.rawConn.RawInput.Bytes()) + return err +} diff --git a/common/ktls/ktls_read_wait.go b/common/ktls/ktls_read_wait.go new file mode 100644 index 00000000..8c1a8ff0 --- /dev/null +++ b/common/ktls/ktls_read_wait.go @@ -0,0 +1,41 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "github.com/sagernet/sing/common/buf" + N "github.com/sagernet/sing/common/network" +) + +func (c *Conn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { + c.readWaitOptions = options + return false +} + +func (c *Conn) WaitReadBuffer() (buffer *buf.Buffer, err error) { + c.rawConn.In.Lock() + defer c.rawConn.In.Unlock() + for c.rawConn.Input.Len() == 0 { + err = c.readRecord() + if err != nil { + return + } + } + buffer = c.readWaitOptions.NewBuffer() + n, err := c.rawConn.Input.Read(buffer.FreeBytes()) + if err != nil { + buffer.Release() + return + } + buffer.Truncate(n) + if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 && + c.rawConn.RawInput.Bytes()[0] == recordTypeAlert { + _ = c.rawConn.ReadRecord() + } + c.readWaitOptions.PostReturn(buffer) + return +} diff --git a/common/ktls/ktls_stub.go b/common/ktls/ktls_stub.go new file mode 100644 index 00000000..66d3e88a --- /dev/null +++ b/common/ktls/ktls_stub.go @@ -0,0 +1,15 @@ +//go:build !linux || !go1.25 || without_badtls + +package ktls + +import ( + "context" + "os" + + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + return nil, os.ErrInvalid +} diff --git a/common/ktls/ktls_write.go b/common/ktls/ktls_write.go new file mode 100644 index 00000000..6f04ca29 --- /dev/null +++ b/common/ktls/ktls_write.go @@ -0,0 +1,154 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.25 && !without_badtls + +package ktls + +import ( + "crypto/cipher" + "crypto/tls" + "errors" + "net" +) + +func (c *Conn) Write(b []byte) (int, error) { + if !c.kernelTx { + return c.Conn.Write(b) + } + // interlock with Close below + for { + x := c.rawConn.ActiveCall.Load() + if x&1 != 0 { + return 0, net.ErrClosed + } + if c.rawConn.ActiveCall.CompareAndSwap(x, x+2) { + break + } + } + defer c.rawConn.ActiveCall.Add(-2) + + //if err := c.Conn.HandshakeContext(context.Background()); err != nil { + // return 0, err + //} + + c.rawConn.Out.Lock() + defer c.rawConn.Out.Unlock() + + if err := *c.rawConn.Out.Err; err != nil { + return 0, err + } + + if !c.rawConn.IsHandshakeComplete.Load() { + return 0, tls.AlertError(alertInternalError) + } + + if *c.rawConn.CloseNotifySent { + // return 0, errShutdown + return 0, errors.New("tls: protocol is shutdown") + } + + // TLS 1.0 is susceptible to a chosen-plaintext + // attack when using block mode ciphers due to predictable IVs. + // This can be prevented by splitting each Application Data + // record into two records, effectively randomizing the RawIV. + // + // https://www.openssl.org/~bodo/tls-cbc.txt + // https://bugzilla.mozilla.org/show_bug.cgi?id=665814 + // https://www.imperialviolet.org/2012/01/15/beastfollowup.html + + var m int + if len(b) > 1 && *c.rawConn.Vers == tls.VersionTLS10 { + if _, ok := (*c.rawConn.Out.Cipher).(cipher.BlockMode); ok { + n, err := c.writeRecordLocked(recordTypeApplicationData, b[:1]) + if err != nil { + return n, c.rawConn.Out.SetErrorLocked(err) + } + m, b = 1, b[1:] + } + } + + n, err := c.writeRecordLocked(recordTypeApplicationData, b) + return n + m, c.rawConn.Out.SetErrorLocked(err) +} + +func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) { + if !c.kernelTx { + return c.rawConn.WriteRecordLocked(typ, data) + } + /*for len(data) > 0 { + m := len(data) + if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload { + m = maxPayload + } + _, err = c.writeKernelRecord(typ, data[:m]) + if err != nil { + return + } + n += m + data = data[m:] + }*/ + return c.writeKernelRecord(typ, data) +} + +const ( + // tcpMSSEstimate is a conservative estimate of the TCP maximum segment + // size (MSS). A constant is used, rather than querying the kernel for + // the actual MSS, to avoid complexity. The value here is the IPv6 + // minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40 + // bytes) and a TCP header with timestamps (32 bytes). + tcpMSSEstimate = 1208 + + // recordSizeBoostThreshold is the number of bytes of application data + // sent after which the TLS record size will be increased to the + // maximum. + recordSizeBoostThreshold = 128 * 1024 +) + +func (c *Conn) maxPayloadSizeForWrite(typ uint16) int { + if /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData { + return maxPlaintext + } + + if *c.rawConn.PacketsSent >= recordSizeBoostThreshold { + return maxPlaintext + } + + // Subtract TLS overheads to get the maximum payload size. + payloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen() + if rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil { + switch ciph := rawCipher.(type) { + case cipher.Stream: + payloadBytes -= (*c.rawConn.Out.Mac).Size() + case cipher.AEAD: + payloadBytes -= ciph.Overhead() + /*case cbcMode: + blockSize := ciph.BlockSize() + // The payload must fit in a multiple of blockSize, with + // room for at least one padding byte. + payloadBytes = (payloadBytes & ^(blockSize - 1)) - 1 + // The RawMac is appended before padding so affects the + // payload size directly. + payloadBytes -= c.out.mac.Size()*/ + default: + panic("unknown cipher type") + } + } + if *c.rawConn.Vers == tls.VersionTLS13 { + payloadBytes-- // encrypted ContentType + } + + // Allow packet growth in arithmetic progression up to max. + pkt := *c.rawConn.PacketsSent + *c.rawConn.PacketsSent++ + if pkt > 1000 { + return maxPlaintext // avoid overflow in multiply below + } + + n := payloadBytes * int(pkt+1) + if n > maxPlaintext { + n = maxPlaintext + } + return n +} diff --git a/common/tls/client.go b/common/tls/client.go index e372d183..19d3a04a 100644 --- a/common/tls/client.go +++ b/common/tls/client.go @@ -10,32 +10,33 @@ import ( "github.com/sagernet/sing-box/common/badtls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" ) -func NewDialerFromOptions(ctx context.Context, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { +func NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { if !options.Enabled { return dialer, nil } - config, err := NewClient(ctx, serverAddress, options) + config, err := NewClient(ctx, logger, serverAddress, options) if err != nil { return nil, err } return NewDialer(dialer, config), nil } -func NewClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { +func NewClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { if !options.Enabled { return nil, nil } if options.Reality != nil && options.Reality.Enabled { - return NewRealityClient(ctx, serverAddress, options) + return NewRealityClient(ctx, logger, serverAddress, options) } else if options.UTLS != nil && options.UTLS.Enabled { - return NewUTLSClient(ctx, serverAddress, options) + return NewUTLSClient(ctx, logger, serverAddress, options) } - return NewSTDClient(ctx, serverAddress, options) + return NewSTDClient(ctx, logger, serverAddress, options) } func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { diff --git a/common/tls/ktls.go b/common/tls/ktls.go new file mode 100644 index 00000000..dd564f53 --- /dev/null +++ b/common/tls/ktls.go @@ -0,0 +1,67 @@ +package tls + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/common/ktls" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +type KTLSClientConfig struct { + Config + logger logger.ContextLogger + kernelTx, kernelRx bool +} + +func (w *KTLSClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + tlsConn, err := aTLS.ClientHandshake(ctx, conn, w.Config) + if err != nil { + return nil, err + } + kConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx) + if err != nil { + tlsConn.Close() + return nil, E.Cause(err, "initialize kernel TLS") + } + return kConn, nil +} + +func (w *KTLSClientConfig) Clone() Config { + return &KTLSClientConfig{ + w.Config.Clone(), + w.logger, + w.kernelTx, + w.kernelRx, + } +} + +type KTlSServerConfig struct { + ServerConfig + logger logger.ContextLogger + kernelTx, kernelRx bool +} + +func (w *KTlSServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { + tlsConn, err := aTLS.ServerHandshake(ctx, conn, w.ServerConfig) + if err != nil { + return nil, err + } + kConn, err := ktls.NewConn(ctx, w.logger, tlsConn, w.kernelTx, w.kernelRx) + if err != nil { + tlsConn.Close() + return nil, E.Cause(err, "initialize kernel TLS") + } + return kConn, nil +} + +func (w *KTlSServerConfig) Clone() Config { + return &KTlSServerConfig{ + w.ServerConfig.Clone().(ServerConfig), + w.logger, + w.kernelTx, + w.kernelRx, + } +} diff --git a/common/tls/reality_client.go b/common/tls/reality_client.go index d056966c..2dcede57 100644 --- a/common/tls/reality_client.go +++ b/common/tls/reality_client.go @@ -32,6 +32,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/ntp" aTLS "github.com/sagernet/sing/common/tls" @@ -49,12 +50,12 @@ type RealityClientConfig struct { shortID [8]byte } -func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) { +func NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) { if options.UTLS == nil || !options.UTLS.Enabled { return nil, E.New("uTLS is required by reality client") } - uClient, err := NewUTLSClient(ctx, serverAddress, options) + uClient, err := NewUTLSClient(ctx, logger, serverAddress, options) if err != nil { return nil, err } @@ -93,7 +94,7 @@ func (e *RealityClientConfig) SetNextProtos(nextProto []string) { e.uClient.SetNextProtos(nextProto) } -func (e *RealityClientConfig) Config() (*STDConfig, error) { +func (e *RealityClientConfig) STDConfig() (*STDConfig, error) { return nil, E.New("unsupported usage for reality") } diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 3a3ae99e..f332784d 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -119,6 +119,13 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) } + if options.ECH != nil && options.ECH.Enabled { + return nil, E.New("Reality is conflict with ECH") + } + if options.KernelRx || options.KernelTx { + return nil, E.New("Reality is conflict with kTLS") + } + return &RealityServerConfig{&tlsConfig}, nil } @@ -138,7 +145,7 @@ func (c *RealityServerConfig) SetNextProtos(nextProto []string) { c.config.NextProtos = nextProto } -func (c *RealityServerConfig) Config() (*tls.Config, error) { +func (c *RealityServerConfig) STDConfig() (*tls.Config, error) { return nil, E.New("unsupported usage for reality") } diff --git a/common/tls/server.go b/common/tls/server.go index bcc5ddfa..aec92bcd 100644 --- a/common/tls/server.go +++ b/common/tls/server.go @@ -12,7 +12,7 @@ import ( aTLS "github.com/sagernet/sing/common/tls" ) -func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { +func NewServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { if !options.Enabled { return nil, nil } diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 0f855228..8aebc3f6 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -11,8 +11,10 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tlsfragment" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/ntp" ) @@ -40,7 +42,7 @@ func (c *STDClientConfig) SetNextProtos(nextProto []string) { c.config.NextProtos = nextProto } -func (c *STDClientConfig) Config() (*STDConfig, error) { +func (c *STDClientConfig) STDConfig() (*STDConfig, error) { return c.config, nil } @@ -52,7 +54,13 @@ func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) { } func (c *STDClientConfig) Clone() Config { - return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment} + return &STDClientConfig{ + ctx: c.ctx, + config: c.config.Clone(), + fragment: c.fragment, + fragmentFallbackDelay: c.fragmentFallbackDelay, + recordFragment: c.recordFragment, + } } func (c *STDClientConfig) ECHConfigList() []byte { @@ -63,7 +71,7 @@ func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList } -func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { +func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { var serverName string if options.ServerName != "" { serverName = options.ServerName @@ -146,10 +154,24 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb } tlsConfig.RootCAs = certPool } - stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment} + var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment} if options.ECH != nil && options.ECH.Enabled { - return parseECHClientConfig(ctx, stdConfig, options) - } else { - return stdConfig, nil + var err error + config, err = parseECHClientConfig(ctx, config.(ECHCapableConfig), options) + if err != nil { + return nil, err + } } + if options.KernelRx || options.KernelTx { + if !C.IsLinux { + return nil, E.New("kTLS is only supported on Linux") + } + config = &KTLSClientConfig{ + Config: config, + logger: logger, + kernelTx: options.KernelTx, + kernelRx: options.KernelRx, + } + } + return config, nil } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 94774179..2ce87301 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -69,7 +69,7 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) { c.config = config } -func (c *STDServerConfig) Config() (*STDConfig, error) { +func (c *STDServerConfig) STDConfig() (*STDConfig, error) { return c.config, nil } @@ -182,7 +182,7 @@ func (c *STDServerConfig) Close() error { return nil } -func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { +func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { if !options.Enabled { return nil, nil } @@ -299,5 +299,14 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound defer serverConfig.access.Unlock() return serverConfig.config, nil } - return serverConfig, nil + var config ServerConfig = serverConfig + if options.KernelTx || options.KernelRx { + config = &KTlSServerConfig{ + ServerConfig: config, + logger: logger, + kernelTx: options.KernelTx, + kernelRx: options.KernelRx, + } + } + return config, nil } diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index fceb15b8..dbc15175 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -14,8 +14,10 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tlsfragment" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/ntp" utls "github.com/metacubex/utls" @@ -50,7 +52,7 @@ func (c *UTLSClientConfig) SetNextProtos(nextProto []string) { c.config.NextProtos = nextProto } -func (c *UTLSClientConfig) Config() (*STDConfig, error) { +func (c *UTLSClientConfig) STDConfig() (*STDConfig, error) { return nil, E.New("unsupported usage for uTLS") } @@ -139,7 +141,7 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error { return c.UConn.HandshakeContext(ctx) } -func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { +func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { var serverName string if options.ServerName != "" { serverName = options.ServerName @@ -214,15 +216,31 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out if err != nil { return nil, err } - uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment} + var config Config = &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment} if options.ECH != nil && options.ECH.Enabled { if options.Reality != nil && options.Reality.Enabled { return nil, E.New("Reality is conflict with ECH") } - return parseECHClientConfig(ctx, uConfig, options) - } else { - return uConfig, nil + config, err = parseECHClientConfig(ctx, config.(ECHCapableConfig), options) + if err != nil { + return nil, err + } } + if options.KernelRx || options.KernelTx { + if options.Reality != nil && options.Reality.Enabled { + return nil, E.New("Reality is conflict with kTLS") + } + if !C.IsLinux { + return nil, E.New("kTLS is only supported on Linux") + } + config = &KTLSClientConfig{ + Config: config, + logger: logger, + kernelTx: options.KernelTx, + kernelRx: options.KernelRx, + } + } + return config, nil } var ( diff --git a/dns/transport/https.go b/dns/transport/https.go index 30c2a11f..95fe7ed1 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -57,7 +57,7 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options } tlsOptions := common.PtrValueOrDefault(options.TLS) tlsOptions.Enabled = true - tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) if err != nil { return nil, err } diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index fd1591a3..e81e6d15 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -51,11 +51,11 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options } tlsOptions := common.PtrValueOrDefault(options.TLS) tlsOptions.Enabled = true - tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) if err != nil { return nil, err } - stdConfig, err := tlsConfig.Config() + stdConfig, err := tlsConfig.STDConfig() if err != nil { return nil, err } diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index 515aff58..39bbab8e 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -48,7 +48,7 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options } tlsOptions := common.PtrValueOrDefault(options.TLS) tlsOptions.Enabled = true - tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) if err != nil { return nil, err } diff --git a/dns/transport/tls.go b/dns/transport/tls.go index 9cb35fd9..932a72a8 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -49,7 +49,7 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o } tlsOptions := common.PtrValueOrDefault(options.TLS) tlsOptions.Enabled = true - tlsConfig, err := tls.NewClient(ctx, options.Server, tlsOptions) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, tlsOptions) if err != nil { return nil, err } diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 5f9fdbe7..a2db6aae 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -1,7 +1,12 @@ --- -icon: material/alert-decagram +icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_rx](#kernel_rx) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [fragment](#fragment) @@ -28,6 +33,8 @@ icon: material/alert-decagram "certificate_path": "", "key": [], "key_path": "", + "kernel_tx": false, + "kernel_rx": false, "acme": { "domain": [], "data_directory": "", @@ -188,7 +195,8 @@ By default, the maximum version is currently TLS 1.3. #### cipher_suites -A list of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored. Note that TLS 1.3 cipher suites are not configurable. +A list of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored. +Note that TLS 1.3 cipher suites are not configurable. If empty, a safe default list is used. The default cipher suites might change over time. @@ -220,6 +228,50 @@ The server private key line array, in PEM format. The path to the server private key, in PEM format. +#### kernel_tx + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux 5.1+, use a newer kernel if possible. + +!!! quote "" + + Only TLS 1.3 is supported. + +!!! warning "" + + uTLS is compatible, but not other custom TLS. + +!!! warning "" + + kTLS TX may only improve performance when `splice(2)` is available (both ends must be TCP or TLS without additional protocols after handshake); otherwise, it will definitely degrade performance. + +Enable kernel TLS transmit support. + +#### kernel_rx + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux 5.1+, use a newer kernel if possible. + +!!! quote "" + + Only TLS 1.3 is supported. + +!!! warning "" + + uTLS is compatible, but not other custom TLS. + +!!! failure "" + + kTLS RX will definitely degrade performance even if `splice(2)` is in use, so enabling it is not recommended. + +Enable kernel TLS receive support. + ## Custom TLS support !!! info "QUIC support" diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 63104d51..8b4ddccb 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -2,6 +2,11 @@ icon: material/alert-decagram --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_rx](#kernel_rx) + !!! quote "sing-box 1.12.0 中的更改" :material-plus: [tls_fragment](#tls_fragment) @@ -28,6 +33,8 @@ icon: material/alert-decagram "certificate_path": "", "key": [], "key_path": "", + "kernel_tx": false, + "kernel_rx": false, "acme": { "domain": [], "data_directory": "", @@ -216,6 +223,56 @@ TLS 版本值: 服务器 PEM 私钥路径。 +#### kernel_tx + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux 5.1+,如果可能,使用较新的内核。 + +!!! quote "" + + 仅支持 TLS 1.3。 + +!!! warning "" + + 兼容 uTLS,但不兼容其他自定义 TLS。 + +!!! warning "" + + kTLS TX 仅当 `splice(2)` 可用时(两端经过握手后必须为没有附加协议的 TCP 或 TLS)才能提高性能;否则肯定会降低性能。 + +启用内核 TLS 发送支持。 + +#### kernel_rx + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux 5.1+,如果可能,使用较新的内核。 + +!!! quote "" + + 仅支持 TLS 1.3。 + +!!! warning "" + + 兼容 uTLS,但不兼容其他自定义 TLS。 + +!!! failure "" + + 即使使用 `splice(2)`,kTLS RX 也肯定会降低性能,因此不建议启用。 + +启用内核 TLS 接收支持。 + +## 自定义 TLS 支持 + +!!! info "QUIC 支持" + + 只有 ECH 在 QUIC 中被支持. + #### utls ==仅客户端== diff --git a/option/tls.go b/option/tls.go index 1c09527c..db51ed1a 100644 --- a/option/tls.go +++ b/option/tls.go @@ -14,6 +14,8 @@ type InboundTLSOptions struct { CertificatePath string `json:"certificate_path,omitempty"` Key badoption.Listable[string] `json:"key,omitempty"` KeyPath string `json:"key_path,omitempty"` + KernelTx bool `json:"kernel_tx,omitempty"` + KernelRx bool `json:"kernel_rx,omitempty"` ACME *InboundACMEOptions `json:"acme,omitempty"` ECH *InboundECHOptions `json:"ech,omitempty"` Reality *InboundRealityOptions `json:"reality,omitempty"` @@ -50,6 +52,8 @@ type OutboundTLSOptions struct { Fragment bool `json:"fragment,omitempty"` FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"` RecordFragment bool `json:"record_fragment,omitempty"` + KernelTx bool `json:"kernel_tx,omitempty"` + KernelRx bool `json:"kernel_rx,omitempty"` ECH *OutboundECHOptions `json:"ech,omitempty"` UTLS *OutboundUTLSOptions `json:"utls,omitempty"` Reality *OutboundRealityOptions `json:"reality,omitempty"` diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go index 6b9e047c..2f24c2ef 100644 --- a/protocol/anytls/outbound.go +++ b/protocol/anytls/outbound.go @@ -52,7 +52,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, E.New("tcp_fast_open is not supported with anytls outbound") } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/http/outbound.go b/protocol/http/outbound.go index 3b631b39..48c4be6b 100644 --- a/protocol/http/outbound.go +++ b/protocol/http/outbound.go @@ -34,7 +34,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - detour, err := tls.NewDialerFromOptions(ctx, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS)) + detour, err := tls.NewDialerFromOptions(ctx, logger, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index 42a37ee6..bcadd878 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -43,7 +43,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index c805f07e..d4382fdc 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -44,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 6354f011..4ffca7e2 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -91,6 +91,10 @@ func (n *Inbound) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } + tlsConfig, err = n.tlsConfig.STDConfig() + if err != nil { + return err + } } if common.Contains(n.network, N.NetworkTCP) { tcpListener, err := n.listener.ListenTCP() diff --git a/protocol/shadowtls/outbound.go b/protocol/shadowtls/outbound.go index 0731b033..41a4a601 100644 --- a/protocol/shadowtls/outbound.go +++ b/protocol/shadowtls/outbound.go @@ -43,7 +43,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL options.TLS.MinVersion = "1.2" options.TLS.MaxVersion = "1.2" } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } @@ -61,7 +61,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig)) } } else { - stdTLSConfig, err := tlsConfig.Config() + stdTLSConfig, err := tlsConfig.STDConfig() if err != nil { return nil, err } diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go index 51115717..521bb551 100644 --- a/protocol/tailscale/dns_transport.go +++ b/protocol/tailscale/dns_transport.go @@ -166,7 +166,7 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn if serverAddr.Port == 0 { serverAddr.Port = 443 } - tlsConfig := common.Must1(tls.NewClient(t.ctx, serverAddr.AddrString(), option.OutboundTLSOptions{ + tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.AddrString(), option.OutboundTLSOptions{ ALPN: []string{http2.NextProtoTLS, "http/1.1"}, })) return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index dc2e0fe4..779d437c 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -51,7 +51,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL key: trojan.Key(options.Password), } if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/tuic/outbound.go b/protocol/tuic/outbound.go index a31d4850..94d3cb77 100644 --- a/protocol/tuic/outbound.go +++ b/protocol/tuic/outbound.go @@ -43,7 +43,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired } - tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index a96d12a0..e746c5fe 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -53,7 +53,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL serverAddr: options.ServerOptions.Build(), } if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index 716570f3..cb5c674f 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -53,7 +53,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL serverAddr: options.ServerOptions.Build(), } if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) + outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } diff --git a/release/local/debug.sh b/release/local/debug.sh index d6bd3057..d649bed4 100755 --- a/release/local/debug.sh +++ b/release/local/debug.sh @@ -13,7 +13,7 @@ pushd $PROJECT git fetch git reset FETCH_HEAD --hard git clean -fdx -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_acme,debug ./cmd/sing-box +go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_acme,debug ./cmd/sing-box popd sudo systemctl stop sing-box diff --git a/release/local/install.sh b/release/local/install.sh index 24e9d006..3aa3d976 100755 --- a/release/local/install.sh +++ b/release/local/install.sh @@ -10,7 +10,7 @@ DIR=$(dirname "$0") PROJECT=$DIR/../.. pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box +go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box popd sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ diff --git a/release/local/reinstall.sh b/release/local/reinstall.sh index 71d07109..04cef16b 100755 --- a/release/local/reinstall.sh +++ b/release/local/reinstall.sh @@ -10,7 +10,7 @@ DIR=$(dirname "$0") PROJECT=$DIR/../.. pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box +go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box popd sudo systemctl stop sing-box diff --git a/route/conn.go b/route/conn.go index 3d2b8f05..e6fbaafd 100644 --- a/route/conn.go +++ b/route/conn.go @@ -102,6 +102,8 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co m.connections.Remove(element) }) var done atomic.Bool + m.preConnectionCopy(ctx, conn, remoteConn, false, &done, onClose) + m.preConnectionCopy(ctx, remoteConn, conn, true, &done, onClose) go m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose) go m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose) } @@ -224,6 +226,24 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) } +func (m *ConnectionManager) preConnectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { + if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destination); isEarlyConn && earlyConn.NeedHandshake() { + err := m.connectionCopyEarly(source, destination) + if err != nil { + if done.Swap(true) { + onClose(err) + } + common.Close(source, destination) + if !direction { + m.logger.ErrorContext(ctx, "connection upload handshake: ", err) + } else { + m.logger.ErrorContext(ctx, "connection download handshake: ", err) + } + return + } + } +} + func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { var ( sourceReader io.Reader = source @@ -262,21 +282,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, } break } - if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destinationWriter); isEarlyConn && earlyConn.NeedHandshake() { - err := m.connectionCopyEarly(source, destination) - if err != nil { - if done.Swap(true) { - onClose(err) - } - common.Close(source, destination) - if !direction { - m.logger.ErrorContext(ctx, "connection upload handshake: ", err) - } else { - m.logger.ErrorContext(ctx, "connection download handshake: ", err) - } - return - } - } + _, err := bufio.CopyWithCounters(destinationWriter, sourceReader, source, readCounters, writeCounters, bufio.DefaultIncreaseBufferAfter, bufio.DefaultBatchSize) if err != nil { common.Close(source, destination) diff --git a/service/derp/service.go b/service/derp/service.go index 959cfa67..686afc7c 100644 --- a/service/derp/service.go +++ b/service/derp/service.go @@ -307,11 +307,11 @@ func (d *Service) startMeshWithHost(derpServer *derp.Server, server *option.DERP } var stdConfig *tls.STDConfig if server.TLS != nil && server.TLS.Enabled { - tlsConfig, err := tls.NewClient(d.ctx, hostname, common.PtrValueOrDefault(server.TLS)) + tlsConfig, err := tls.NewClient(d.ctx, d.logger, hostname, common.PtrValueOrDefault(server.TLS)) if err != nil { return err } - stdConfig, err = tlsConfig.Config() + stdConfig, err = tlsConfig.STDConfig() if err != nil { return err } diff --git a/service/resolved/transport.go b/service/resolved/transport.go index eb756fdb..c54a6341 100644 --- a/service/resolved/transport.go +++ b/service/resolved/transport.go @@ -129,7 +129,7 @@ func (t *Transport) updateTransports(link *TransportLink) error { return os.ErrInvalid } if link.dnsOverTLS { - tlsConfig := common.Must1(tls.NewClient(t.ctx, serverAddr.String(), option.OutboundTLSOptions{ + tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.String(), option.OutboundTLSOptions{ Enabled: true, ServerName: serverAddr.String(), })) @@ -151,7 +151,7 @@ func (t *Transport) updateTransports(link *TransportLink) error { } else { serverName = serverAddr.String() } - tlsConfig := common.Must1(tls.NewClient(t.ctx, serverAddr.String(), option.OutboundTLSOptions{ + tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.String(), option.OutboundTLSOptions{ Enabled: true, ServerName: serverName, })) diff --git a/transport/sip003/v2ray.go b/transport/sip003/v2ray.go index d7b752f6..f35e2654 100644 --- a/transport/sip003/v2ray.go +++ b/transport/sip003/v2ray.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-vmess" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) @@ -55,7 +56,7 @@ func newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router, var tlsClient tls.Config var err error if tlsOptions.Enabled { - tlsClient, err = tls.NewClient(ctx, serverAddr.AddrString(), tlsOptions) + tlsClient, err = tls.NewClient(ctx, logger.NOP(), serverAddr.AddrString(), tlsOptions) if err != nil { return nil, err } diff --git a/transport/trojan/protocol.go b/transport/trojan/protocol.go index e13dda67..7c12201e 100644 --- a/transport/trojan/protocol.go +++ b/transport/trojan/protocol.go @@ -83,6 +83,14 @@ func (c *ClientConn) Upstream() any { return c.ExtendedConn } +func (c *ClientConn) ReaderReplaceable() bool { + return c.headerWritten +} + +func (c *ClientConn) WriterReplaceable() bool { + return c.headerWritten +} + type ClientPacketConn struct { net.Conn access sync.Mutex From 9110851af344b187631a8e58797b33301334b1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 8 Sep 2025 19:35:17 +0800 Subject: [PATCH 021/185] ktls: Add warning for inappropriate scenarios --- common/tls/client.go | 44 +++++++++++++++++++++++++++++++------ common/tls/server.go | 31 ++++++++++++++++++++++---- common/tls/std_server.go | 4 ++++ protocol/http/inbound.go | 7 +++++- protocol/mixed/inbound.go | 7 +++++- protocol/trojan/inbound.go | 8 ++++++- protocol/trojan/outbound.go | 9 +++++++- protocol/vless/inbound.go | 11 +++++++++- protocol/vless/outbound.go | 10 ++++++++- 9 files changed, 114 insertions(+), 17 deletions(-) diff --git a/common/tls/client.go b/common/tls/client.go index 19d3a04a..83969954 100644 --- a/common/tls/client.go +++ b/common/tls/client.go @@ -20,7 +20,12 @@ func NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dial if !options.Enabled { return dialer, nil } - config, err := NewClient(ctx, logger, serverAddress, options) + config, err := NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: serverAddress, + Options: options, + }) if err != nil { return nil, err } @@ -28,15 +33,40 @@ func NewDialerFromOptions(ctx context.Context, logger logger.ContextLogger, dial } func NewClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - if !options.Enabled { + return NewClientWithOptions(ClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: serverAddress, + Options: options, + }) +} + +type ClientOptions struct { + Context context.Context + Logger logger.ContextLogger + ServerAddress string + Options option.OutboundTLSOptions + KTLSCompatible bool +} + +func NewClientWithOptions(options ClientOptions) (Config, error) { + if !options.Options.Enabled { return nil, nil } - if options.Reality != nil && options.Reality.Enabled { - return NewRealityClient(ctx, logger, serverAddress, options) - } else if options.UTLS != nil && options.UTLS.Enabled { - return NewUTLSClient(ctx, logger, serverAddress, options) + if !options.KTLSCompatible { + if options.Options.KernelTx { + options.Logger.Warn("enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx") + } } - return NewSTDClient(ctx, logger, serverAddress, options) + if options.Options.KernelRx { + options.Logger.Warn("enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx") + } + if options.Options.Reality != nil && options.Options.Reality.Enabled { + return NewRealityClient(options.Context, options.Logger, options.ServerAddress, options.Options) + } else if options.Options.UTLS != nil && options.Options.UTLS.Enabled { + return NewUTLSClient(options.Context, options.Logger, options.ServerAddress, options.Options) + } + return NewSTDClient(options.Context, options.Logger, options.ServerAddress, options.Options) } func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { diff --git a/common/tls/server.go b/common/tls/server.go index aec92bcd..74b240fc 100644 --- a/common/tls/server.go +++ b/common/tls/server.go @@ -12,14 +12,37 @@ import ( aTLS "github.com/sagernet/sing/common/tls" ) +type ServerOptions struct { + Context context.Context + Logger log.ContextLogger + Options option.InboundTLSOptions + KTLSCompatible bool +} + func NewServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { - if !options.Enabled { + return NewServerWithOptions(ServerOptions{ + Context: ctx, + Logger: logger, + Options: options, + }) +} + +func NewServerWithOptions(options ServerOptions) (ServerConfig, error) { + if !options.Options.Enabled { return nil, nil } - if options.Reality != nil && options.Reality.Enabled { - return NewRealityServer(ctx, logger, options) + if !options.KTLSCompatible { + if options.Options.KernelTx { + options.Logger.Warn("enabling kTLS TX in current scenarios will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_tx") + } } - return NewSTDServer(ctx, logger, options) + if options.Options.KernelRx { + options.Logger.Warn("enabling kTLS RX will definitely reduce performance, please checkout https://sing-box.sagernet.org/configuration/shared/tls/#kernel_rx") + } + if options.Options.Reality != nil && options.Options.Reality.Enabled { + return NewRealityServer(options.Context, options.Logger, options.Options) + } + return NewSTDServer(options.Context, options.Logger, options.Options) } func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) { diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 2ce87301..d162ceb7 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -11,6 +11,7 @@ import ( "github.com/sagernet/fswatch" "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" "github.com/sagernet/sing/common" @@ -301,6 +302,9 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option. } var config ServerConfig = serverConfig if options.KernelTx || options.KernelRx { + if !C.IsLinux { + return nil, E.New("kTLS is only supported on Linux") + } config = &KTlSServerConfig{ ServerConfig: config, logger: logger, diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go index 68150fa7..e8a9a3da 100644 --- a/protocol/http/inbound.go +++ b/protocol/http/inbound.go @@ -43,7 +43,12 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo authenticator: auth.NewAuthenticator(options.Users), } if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ + Context: ctx, + Logger: logger, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: true, + }) if err != nil { return nil, err } diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index 5b531480..5591c315 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -46,7 +46,12 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo authenticator: auth.NewAuthenticator(options.Users), } if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ + Context: ctx, + Logger: logger, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: true, + }) if err != nil { return nil, err } diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index ec95a81e..24e8a023 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -50,7 +50,13 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo users: options.Users, } if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ + Context: ctx, + Logger: logger, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" && + !common.PtrValueOrDefault(options.Multiplex).Enabled, + }) if err != nil { return nil, err } diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index 779d437c..26c7c81f 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -51,7 +51,14 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL key: trojan.Key(options.Password), } if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) + outbound.tlsConfig, err = tls.NewClientWithOptions(tls.ClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: options.Server, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" && + !common.PtrValueOrDefault(options.Multiplex).Enabled, + }) if err != nil { return nil, err } diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index 3cc53db4..1df7cb01 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -68,7 +68,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo })) inbound.service = service if options.TLS != nil { - inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + inbound.tlsConfig, err = tls.NewServerWithOptions(tls.ServerOptions{ + Context: ctx, + Logger: logger, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" && + !common.PtrValueOrDefault(options.Multiplex).Enabled && + common.All(options.Users, func(it option.VLESSUser) bool { + return it.Flow == "" + }), + }) if err != nil { return nil, err } diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index e746c5fe..ab774760 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -53,7 +53,15 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL serverAddr: options.ServerOptions.Build(), } if options.TLS != nil { - outbound.tlsConfig, err = tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) + outbound.tlsConfig, err = tls.NewClientWithOptions(tls.ClientOptions{ + Context: ctx, + Logger: logger, + ServerAddress: options.Server, + Options: common.PtrValueOrDefault(options.TLS), + KTLSCompatible: common.PtrValueOrDefault(options.Transport).Type == "" && + !common.PtrValueOrDefault(options.Multiplex).Enabled && + options.Flow == "", + }) if err != nil { return nil, err } From e9c46cc35921d018b594e11750d7f356f6b5b44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 9 Sep 2025 19:20:15 +0800 Subject: [PATCH 022/185] Improve compatibility for kTLS --- .github/workflows/lint.yml | 2 +- .golangci.yml | 2 +- common/ktls/ktls.go | 33 +++- common/tls/reality_client.go | 18 +- common/tls/reality_server.go | 19 +- common/tls/utls_client.go | 6 +- common/tls/utls_stub.go | 5 +- common/urltest/urltest.go | 3 +- docs/configuration/shared/tls.md | 8 - docs/configuration/shared/tls.zh.md | 8 - go.mod | 2 +- go.sum | 4 +- route/conn.go | 35 +++- test/go.mod | 97 +++++---- test/go.sum | 232 +++++++++++----------- test/ktls_test.go | 295 ++++++++++++++++++++++++++++ transport/trojan/protocol.go | 4 +- 17 files changed, 555 insertions(+), 218 deletions(-) create mode 100644 test/ktls_test.go diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 25302bf9..e1485b38 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ~1.24.10 + go-version: ^1.25 - name: golangci-lint uses: golangci/golangci-lint-action@v8 with: diff --git a/.golangci.yml b/.golangci.yml index de2aa5a6..9a20700a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ version: "2" run: - go: "1.24" + go: "1.25" build-tags: - with_gvisor - with_quic diff --git a/common/ktls/ktls.go b/common/ktls/ktls.go index 36f36b25..22db2465 100644 --- a/common/ktls/ktls.go +++ b/common/ktls/ktls.go @@ -3,8 +3,10 @@ package ktls import ( + "bytes" "context" "crypto/tls" + "errors" "io" "net" "os" @@ -15,6 +17,8 @@ import ( "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" aTLS "github.com/sagernet/sing/common/tls" + + "golang.org/x/sys/unix" ) type Conn struct { @@ -85,7 +89,7 @@ func (c *Conn) Upstream() any { return c.Conn } -func (c *Conn) SyscallConnForRead() syscall.Conn { +func (c *Conn) SyscallConnForRead() syscall.RawConn { if !c.kernelRx { return nil } @@ -94,13 +98,34 @@ func (c *Conn) SyscallConnForRead() syscall.Conn { return nil } c.logger.DebugContext(c.ctx, "ktls: RX splice requested") - return c.syscallConn + return c.rawSyscallConn } -func (c *Conn) SyscallConnForWrite() syscall.Conn { +func (c *Conn) HandleSyscallReadError(inputErr error) ([]byte, error) { + if errors.Is(inputErr, unix.EINVAL) { + err := c.readRecord() + if err != nil { + return nil, E.Cause(err, "ktls: handle non-application-data record") + } + var input bytes.Buffer + if c.rawConn.Input.Len() > 0 { + _, err = c.rawConn.Input.WriteTo(&input) + if err != nil { + return nil, err + } + } + return input.Bytes(), nil + } else if errors.Is(inputErr, unix.EBADMSG) { + return nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertBadRecordMAC)) + } else { + return nil, E.Cause(inputErr, "ktls: unexpected errno") + } +} + +func (c *Conn) SyscallConnForWrite() syscall.RawConn { if !c.kernelTx { return nil } c.logger.DebugContext(c.ctx, "ktls: TX splice requested") - return c.syscallConn + return c.rawSyscallConn } diff --git a/common/tls/reality_client.go b/common/tls/reality_client.go index 2dcede57..9362d2f8 100644 --- a/common/tls/reality_client.go +++ b/common/tls/reality_client.go @@ -28,6 +28,7 @@ import ( "unsafe" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/debug" @@ -50,7 +51,7 @@ type RealityClientConfig struct { shortID [8]byte } -func NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) { +func NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { if options.UTLS == nil || !options.UTLS.Enabled { return nil, E.New("uTLS is required by reality client") } @@ -75,7 +76,20 @@ func NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAd if decodedLen > 8 { return nil, E.New("invalid short_id") } - return &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}, nil + + var config Config = &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID} + if options.KernelRx || options.KernelTx { + if !C.IsLinux { + return nil, E.New("kTLS is only supported on Linux") + } + config = &KTLSClientConfig{ + Config: config, + logger: logger, + kernelTx: options.KernelTx, + kernelRx: options.KernelRx, + } + } + return config, nil } func (e *RealityClientConfig) ServerName() string { diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index f332784d..b0b3e6e3 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -12,6 +12,7 @@ import ( "time" "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" @@ -28,7 +29,7 @@ type RealityServerConfig struct { config *utls.RealityConfig } -func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) { +func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) { var tlsConfig utls.RealityConfig if options.ACME != nil && len(options.ACME.Domain) > 0 { @@ -122,11 +123,19 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb if options.ECH != nil && options.ECH.Enabled { return nil, E.New("Reality is conflict with ECH") } - if options.KernelRx || options.KernelTx { - return nil, E.New("Reality is conflict with kTLS") + var config ServerConfig = &RealityServerConfig{&tlsConfig} + if options.KernelTx || options.KernelRx { + if !C.IsLinux { + return nil, E.New("kTLS is only supported on Linux") + } + config = &KTlSServerConfig{ + ServerConfig: config, + logger: logger, + kernelTx: options.KernelTx, + kernelRx: options.KernelRx, + } } - - return &RealityServerConfig{&tlsConfig}, nil + return config, nil } func (c *RealityServerConfig) ServerName() string { diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index dbc15175..9f8138d7 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -16,6 +16,7 @@ import ( "github.com/sagernet/sing-box/common/tlsfragment" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/ntp" @@ -226,10 +227,7 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre return nil, err } } - if options.KernelRx || options.KernelTx { - if options.Reality != nil && options.Reality.Enabled { - return nil, E.New("Reality is conflict with kTLS") - } + if (options.KernelRx || options.KernelTx) && !common.PtrValueOrDefault(options.Reality).Enabled { if !C.IsLinux { return nil, E.New("kTLS is only supported on Linux") } diff --git a/common/tls/utls_stub.go b/common/tls/utls_stub.go index ea5da4a7..3eddd28e 100644 --- a/common/tls/utls_stub.go +++ b/common/tls/utls_stub.go @@ -8,13 +8,14 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" ) -func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { +func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`) } -func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { +func NewRealityClient(ctx context.Context, logger logger.ContextLogger, serverAddress string, options option.OutboundTLSOptions) (Config, error) { return nil, E.New(`uTLS, which is required by reality is not included in this build, rebuild with -tags with_utls`) } diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index ee8624fd..016f4a50 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -11,7 +11,6 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" @@ -100,7 +99,7 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e return } defer instance.Close() - if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() { + if N.NeedHandshakeForWrite(instance) { start = time.Now() } req, err := http.NewRequest(http.MethodHead, link, nil) diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index a2db6aae..fa53ba12 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -240,10 +240,6 @@ The path to the server private key, in PEM format. Only TLS 1.3 is supported. -!!! warning "" - - uTLS is compatible, but not other custom TLS. - !!! warning "" kTLS TX may only improve performance when `splice(2)` is available (both ends must be TCP or TLS without additional protocols after handshake); otherwise, it will definitely degrade performance. @@ -262,10 +258,6 @@ Enable kernel TLS transmit support. Only TLS 1.3 is supported. -!!! warning "" - - uTLS is compatible, but not other custom TLS. - !!! failure "" kTLS RX will definitely degrade performance even if `splice(2)` is in use, so enabling it is not recommended. diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 8b4ddccb..32f85bee 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -235,10 +235,6 @@ TLS 版本值: 仅支持 TLS 1.3。 -!!! warning "" - - 兼容 uTLS,但不兼容其他自定义 TLS。 - !!! warning "" kTLS TX 仅当 `splice(2)` 可用时(两端经过握手后必须为没有附加协议的 TCP 或 TLS)才能提高性能;否则肯定会降低性能。 @@ -257,10 +253,6 @@ TLS 版本值: 仅支持 TLS 1.3。 -!!! warning "" - - 兼容 uTLS,但不兼容其他自定义 TLS。 - !!! failure "" 即使使用 `splice(2)`,kTLS RX 也肯定会降低性能,因此不建议启用。 diff --git a/go.mod b/go.mod index 5d5d6414..2e21d23f 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 github.com/sagernet/sing-tun v0.8.0-beta.11 - github.com/sagernet/sing-vmess v0.2.7 + github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 github.com/sagernet/wireguard-go v0.0.2-beta.1 diff --git a/go.sum b/go.sum index 35610419..62682a98 100644 --- a/go.sum +++ b/go.sum @@ -171,8 +171,8 @@ github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-tun v0.8.0-beta.11 h1:xVi8VcVkvz2o+3v1PLv5MOkFpiVCwjLjucVlmigDi5c= github.com/sagernet/sing-tun v0.8.0-beta.11/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= -github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk= -github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs= +github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= +github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos= diff --git a/route/conn.go b/route/conn.go index e6fbaafd..82c8c2e9 100644 --- a/route/conn.go +++ b/route/conn.go @@ -227,8 +227,19 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial } func (m *ConnectionManager) preConnectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { - if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destination); isEarlyConn && earlyConn.NeedHandshake() { - err := m.connectionCopyEarly(source, destination) + readHandshake := N.NeedHandshakeForRead(source) + writeHandshake := N.NeedHandshakeForWrite(destination) + if readHandshake || writeHandshake { + var err error + for { + err = m.connectionCopyEarlyWrite(source, destination, readHandshake, writeHandshake) + if err == nil && N.NeedHandshakeForRead(source) { + continue + } else if err == os.ErrInvalid || err == context.DeadlineExceeded { + err = nil + } + break + } if err != nil { if done.Swap(true) { onClose(err) @@ -317,24 +328,32 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, } } -func (m *ConnectionManager) connectionCopyEarly(source net.Conn, destination io.Writer) error { +func (m *ConnectionManager) connectionCopyEarlyWrite(source net.Conn, destination io.Writer, readHandshake bool, writeHandshake bool) error { payload := buf.NewPacket() defer payload.Release() err := source.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout)) if err != nil { if err == os.ErrInvalid { - return common.Error(destination.Write(nil)) + if writeHandshake { + return common.Error(destination.Write(nil)) + } } return err } _, err = payload.ReadOnceFrom(source) - if err != nil && !(E.IsTimeout(err) || errors.Is(err, io.EOF)) { + isTimeout := E.IsTimeout(err) + if err != nil && !(isTimeout || errors.Is(err, io.EOF)) { return E.Cause(err, "read payload") } _ = source.SetReadDeadline(time.Time{}) - _, err = destination.Write(payload.Bytes()) - if err != nil { - return E.Cause(err, "write payload") + if !payload.IsEmpty() || writeHandshake { + _, err = destination.Write(payload.Bytes()) + if err != nil { + return E.Cause(err, "write payload") + } + } + if isTimeout { + return context.DeadlineExceeded } return nil } diff --git a/test/go.mod b/test/go.mod index e6823537..cca30099 100644 --- a/test/go.mod +++ b/test/go.mod @@ -11,16 +11,16 @@ replace github.com/sagernet/sing-box => ../ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 - github.com/gofrs/uuid/v5 v5.3.1 - github.com/sagernet/quic-go v0.49.0-beta.1 - github.com/sagernet/sing v0.6.4-0.20250319121229-11d8838dc56d - github.com/sagernet/sing-quic v0.4.1-beta.1 - github.com/sagernet/sing-shadowsocks v0.2.7 - github.com/sagernet/sing-shadowsocks2 v0.2.0 + github.com/gofrs/uuid/v5 v5.3.2 + github.com/sagernet/quic-go v0.52.0-beta.1 + github.com/sagernet/sing v0.7.8-0.20250909124511-ab3827767cea + github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5 + github.com/sagernet/sing-shadowsocks v0.2.8 + github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/spyzhov/ajson v0.9.4 github.com/stretchr/testify v1.10.0 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.35.0 + golang.org/x/net v0.43.0 ) require ( @@ -30,12 +30,11 @@ require ( github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/anytls/sing-anytls v0.0.6 // indirect + github.com/anytls/sing-anytls v0.0.8 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect - github.com/caddyserver/certmagic v0.21.7 // indirect + github.com/caddyserver/certmagic v0.23.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect - github.com/cloudflare/circl v1.6.0 // indirect - github.com/coder/websocket v1.8.12 // indirect + github.com/coder/websocket v1.8.13 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/cretz/bine v0.2.0 // indirect @@ -48,73 +47,68 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gaissmai/bart v0.11.1 // indirect - github.com/go-chi/chi/v5 v5.2.1 // indirect + github.com/go-chi/chi/v5 v5.2.2 // indirect github.com/go-chi/render v1.0.3 // indirect github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect - github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/illarion/gonotify/v2 v2.0.3 // indirect - github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect + github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect - github.com/libdns/alidns v1.0.3 // indirect - github.com/libdns/cloudflare v0.1.1 // indirect - github.com/libdns/libdns v0.2.2 // indirect + github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect + github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect + github.com/libdns/libdns v1.1.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect - github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect - github.com/mholt/acmez/v3 v3.0.1 // indirect - github.com/miekg/dns v1.1.63 // indirect + github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 // indirect + github.com/metacubex/utls v1.8.0 // indirect + github.com/mholt/acmez/v3 v3.1.2 // indirect + github.com/miekg/dns v1.1.67 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/onsi/ginkgo/v2 v2.17.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.4.1 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/safchain/ethtool v0.3.0 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/fswatch v0.1.1 // indirect - github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // indirect + github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect - github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect - github.com/sagernet/sing-mux v0.3.1 // indirect - github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 // indirect - github.com/sagernet/sing-tun v0.6.2-0.20250319123703-35b5747b44ec // indirect - github.com/sagernet/sing-vmess v0.2.0 // indirect - github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect - github.com/sagernet/tailscale v1.80.3-mod.0 // indirect - github.com/sagernet/utls v1.6.7 // indirect - github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect + github.com/sagernet/sing-mux v0.3.3 // indirect + github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect + github.com/sagernet/sing-tun v0.8.0-beta.1.0.20250909100419-a8cb01e6df93 // indirect + github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect + github.com/sagernet/smux v1.5.34-mod.2 // indirect + github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 // indirect + github.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect @@ -125,33 +119,34 @@ require ( github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect - github.com/vishvananda/netns v0.0.4 // indirect + github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect - go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.32.0 // indirect - go.opentelemetry.io/otel/trace v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/grpc v1.70.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/grpc v1.73.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect diff --git a/test/go.sum b/test/go.sum index ce8bfb28..06bc7792 100644 --- a/test/go.sum +++ b/test/go.sum @@ -12,22 +12,20 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anytls/sing-anytls v0.0.6 h1:UatIjl/OvzWQGXQ1I2bAIkabL9WtihW0fA7G+DXGBUg= -github.com/anytls/sing-anytls v0.0.6/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= +github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6Vymc= +github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg= -github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI= +github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= +github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= -github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= +github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= @@ -35,6 +33,7 @@ github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8 github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= @@ -59,8 +58,8 @@ github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= -github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= +github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= @@ -72,16 +71,14 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0= -github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= +github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -90,14 +87,12 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= @@ -112,29 +107,29 @@ github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= -github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= -github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= +github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU= +github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= -github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= -github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= -github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= -github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= -github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= -github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ= +github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= +github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8= +github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= +github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU= +github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= @@ -145,12 +140,14 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= -github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= -github.com/mholt/acmez/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8= -github.com/mholt/acmez/v3 v3.0.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= -github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc= +github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac= +github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ= +github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= +github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= +github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -161,10 +158,6 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= -github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= -github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= 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= @@ -178,10 +171,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= -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-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= -github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= @@ -190,41 +183,37 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= -github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs= -github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= +github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= +github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+yfK8DQ8Jk= -github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8WHNsRs71b3Lt1+p/U= -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.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.6.4-0.20250319121229-11d8838dc56d h1:8GJnvXlOBdgCa0spumUzPbMamkEbud4sfNTd8+1YaEg= -github.com/sagernet/sing v0.6.4-0.20250319121229-11d8838dc56d/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= -github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78= -github.com/sagernet/sing-quic v0.4.1-beta.1 h1:V2VfMckT3EQR3ZdfSzJgZZDsvfZZH42QAZpnOnHKa0s= -github.com/sagernet/sing-quic v0.4.1-beta.1/go.mod h1:c+CytOEyeN20KCTFIP8YQUkNDVFLSzjrEPqP7Hlnxys= -github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= -github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= -github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= -github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= -github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQAHhSXqAfxAw1wDG/QWbdpGH5Na3k8qUynqWnEA= -github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056/go.mod h1:HyacBPIFiKihJQR8LQp56FM4hBtd/7MZXnRxxQIOPsc= -github.com/sagernet/sing-tun v0.6.2-0.20250319123703-35b5747b44ec h1:9/OYGb9qDmUFIhqd3S+3eni62EKRQR1rSmRH18baA/M= -github.com/sagernet/sing-tun v0.6.2-0.20250319123703-35b5747b44ec/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI= -github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= -github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= -github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= -github.com/sagernet/tailscale v1.80.3-mod.0 h1:oHIdivbR/yxoiA9d3a2rRlhYn2shY9XVF35Rr8jW508= -github.com/sagernet/tailscale v1.80.3-mod.0/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= -github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= -github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM= -github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc= -github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= +github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= +github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= +github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.8-0.20250909124511-ab3827767cea h1:vkWFzPVlqnKq3FMpmh43ZVDbqHWapbv0Sh3vQc8oo7o= +github.com/sagernet/sing v0.7.8-0.20250909124511-ab3827767cea/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= +github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= +github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5 h1:vnRNLE0bBnz5NNbBoFH7NA7mlvNSa2Z4w+1Eb8pyX48= +github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5/go.mod h1:gi/sGED8gTWgTAp3GlzXo2D7mXYY+ERoxtGvSkNx3sI= +github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= +github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= +github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= +github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= +github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= +github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= +github.com/sagernet/sing-tun v0.8.0-beta.1.0.20250909100419-a8cb01e6df93 h1:jGkwe0Uk5litEUnvHO/c0nukm2FqvdwKHJio4kJIOxM= +github.com/sagernet/sing-tun v0.8.0-beta.1.0.20250909100419-a8cb01e6df93/go.mod h1:LokZYuEV3crByjQc/XRohLgfNvybtXdx5qe/I4W6S7k= +github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= +github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= +github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= +github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= +github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 h1:cWM1iPwqIE1t06ft80wpvFB4xbhOpIFI+TFnTw2gnbs= +github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= +github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= +github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -232,7 +221,14 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84= github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= @@ -256,8 +252,8 @@ github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/0 github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -268,22 +264,24 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= 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.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -302,30 +300,30 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 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= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -336,25 +334,25 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 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= @@ -363,17 +361,17 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= -google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/ktls_test.go b/test/ktls_test.go new file mode 100644 index 00000000..c873a162 --- /dev/null +++ b/test/ktls_test.go @@ -0,0 +1,295 @@ +package main + +import ( + "net/netip" + "testing" + + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/json/badoption" + + "github.com/gofrs/uuid/v5" +) + +func TestKTLS(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeTrojan, + Options: &option.TrojanInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []option.TrojanUser{ + { + Name: "sekai", + Password: "password", + }, + }, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + // KernelTx: true, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTrojan, + Tag: "trojan-out", + Options: &option.TrojanOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KernelTx: true, + KernelRx: true, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} + +func TestKTLSECH(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeTrojan, + Options: &option.TrojanInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []option.TrojanUser{ + { + Name: "sekai", + Password: "password", + }, + }, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + KernelTx: true, + ECH: &option.InboundECHOptions{ + Enabled: true, + Key: []string{echKey}, + }, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTrojan, + Tag: "trojan-out", + Options: &option.TrojanOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KernelTx: true, + KernelRx: true, + ECH: &option.OutboundECHOptions{ + Enabled: true, + Config: []string{echConfig}, + }, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "trojan-out", + }, + }, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} + +func TestKTLSReality(t *testing.T) { + user, _ := uuid.NewV4() + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVLESS, + Options: &option.VLESSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []option.VLESSUser{{UUID: user.String()}}, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + KernelTx: true, + Reality: &option.InboundRealityOptions{ + Enabled: true, + Handshake: option.InboundRealityHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "google.com", + ServerPort: 443, + }, + }, + ShortID: []string{"0123456789abcdef"}, + PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", + }, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeVLESS, + Tag: "ss-out", + Options: &option.VLESSOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: user.String(), + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + KernelTx: true, + KernelRx: true, + Reality: &option.OutboundRealityOptions{ + Enabled: true, + ShortID: "0123456789abcdef", + PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", + }, + UTLS: &option.OutboundUTLSOptions{ + Enabled: true, + }, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + + RouteOptions: option.RouteActionOptions{ + Outbound: "ss-out", + }, + }, + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} diff --git a/transport/trojan/protocol.go b/transport/trojan/protocol.go index 7c12201e..6369d86d 100644 --- a/transport/trojan/protocol.go +++ b/transport/trojan/protocol.go @@ -26,7 +26,7 @@ const ( var CRLF = []byte{'\r', '\n'} -var _ N.EarlyConn = (*ClientConn)(nil) +var _ N.EarlyWriter = (*ClientConn)(nil) type ClientConn struct { N.ExtendedConn @@ -43,7 +43,7 @@ func NewClientConn(conn net.Conn, key [KeyLength]byte, destination M.Socksaddr) } } -func (c *ClientConn) NeedHandshake() bool { +func (c *ClientConn) NeedHandshakeForWrite() bool { return !c.headerWritten } From 60d81a73d9f9424d0a45d74d2a8d1c39c81c9f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 9 Sep 2025 22:20:53 +0800 Subject: [PATCH 023/185] Improve ktls rx error handling --- common/ktls/ktls.go | 2 ++ common/ktls/ktls_linux.go | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/common/ktls/ktls.go b/common/ktls/ktls.go index 22db2465..eb2e86cf 100644 --- a/common/ktls/ktls.go +++ b/common/ktls/ktls.go @@ -32,6 +32,7 @@ type Conn struct { readWaitOptions N.ReadWaitOptions kernelTx bool kernelRx bool + pendingRxSplice bool } func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { @@ -103,6 +104,7 @@ func (c *Conn) SyscallConnForRead() syscall.RawConn { func (c *Conn) HandleSyscallReadError(inputErr error) ([]byte, error) { if errors.Is(inputErr, unix.EINVAL) { + c.pendingRxSplice = true err := c.readRecord() if err != nil { return nil, E.Cause(err, "ktls: handle non-application-data record") diff --git a/common/ktls/ktls_linux.go b/common/ktls/ktls_linux.go index 313fe381..bc9fb8b9 100644 --- a/common/ktls/ktls_linux.go +++ b/common/ktls/ktls_linux.go @@ -258,14 +258,14 @@ func (c *Conn) readKernelRecord() (uint8, []byte, error) { var err error er := c.rawSyscallConn.Read(func(fd uintptr) bool { n, err = recvmsg(int(fd), &msg, 0) - return err != unix.EAGAIN + return err != unix.EAGAIN || c.pendingRxSplice }) if er != nil { return 0, nil, er } switch err { case nil: - case syscall.EINVAL: + case syscall.EINVAL, syscall.EAGAIN: return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertProtocolVersion)) case syscall.EMSGSIZE: return 0, nil, c.rawConn.In.SetErrorLocked(c.sendAlert(alertRecordOverflow)) @@ -276,7 +276,7 @@ func (c *Conn) readKernelRecord() (uint8, []byte, error) { } if n <= 0 { - return 0, nil, io.EOF + return 0, nil, c.rawConn.In.SetErrorLocked(io.EOF) } if cmsg.Level == unix.SOL_TLS && cmsg.Type == TLS_GET_RECORD_TYPE { From c530995832aed0e823c2a2af16faf1def7ff30ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 10 Sep 2025 15:54:12 +0800 Subject: [PATCH 024/185] release: Fix linux build --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0f009697..f422e4ec 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -92,7 +92,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "0" From 49056b5060925f9753fb56e35eb4f3d558e2a71d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 11 Sep 2025 19:08:23 +0800 Subject: [PATCH 025/185] Fix ping domain --- route/route.go | 68 +++++++++++++++++++++++++++---- transport/wireguard/device_nat.go | 4 +- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/route/route.go b/route/route.go index 7e87e97f..1d6663d8 100644 --- a/route/route.go +++ b/route/route.go @@ -17,6 +17,7 @@ import ( R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-mux" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -271,6 +272,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire if err != nil { return nil, err } + var directRouteOutbound adapter.DirectRouteOutbound if selectedRule != nil { switch action := selectedRule.Action().(type) { case *R.RuleActionReject: @@ -296,17 +298,69 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire if !common.Contains(outbound.Network(), metadata.Network) { return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound) } - return outbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) + directRouteOutbound = outbound.(adapter.DirectRouteOutbound) } } - if selectedRule != nil || metadata.Network != N.NetworkICMP { - return nil, nil + if directRouteOutbound == nil { + if selectedRule != nil || metadata.Network != N.NetworkICMP { + return nil, nil + } + defaultOutbound := r.outbound.Default() + if !common.Contains(defaultOutbound.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag()) + } + directRouteOutbound = defaultOutbound.(adapter.DirectRouteOutbound) } - defaultOutbound := r.outbound.Default() - if !common.Contains(defaultOutbound.Network(), metadata.Network) { - return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag()) + if metadata.Destination.IsFqdn() { + if len(metadata.DestinationAddresses) == 0 { + var strategy C.DomainStrategy + if metadata.Source.IsIPv4() { + strategy = C.DomainStrategyIPv4Only + } else { + strategy = C.DomainStrategyIPv6Only + } + err = r.actionResolve(r.ctx, &metadata, &R.RuleActionResolve{ + Strategy: strategy, + }) + if err != nil { + return nil, err + } + } + var newDestination netip.Addr + if metadata.Source.IsIPv4() { + for _, address := range metadata.DestinationAddresses { + if address.Is4() { + newDestination = address + break + } + } + } else { + for _, address := range metadata.DestinationAddresses { + if address.Is6() { + newDestination = address + break + } + } + } + if !newDestination.IsValid() { + if metadata.Source.IsIPv4() { + return nil, E.New("no IPv4 address found for domain: ", metadata.Destination.Fqdn) + } else { + return nil, E.New("no IPv6 address found for domain: ", metadata.Destination.Fqdn) + } + } + metadata.Destination = M.Socksaddr{ + Addr: newDestination, + } + routeContext = ping.NewContextDestinationWriter(routeContext, metadata.OriginDestination.Addr) + var routeDestination tun.DirectRouteDestination + routeDestination, err = directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout) + if err != nil { + return nil, err + } + return ping.NewDestinationWriter(routeDestination, newDestination), nil } - return defaultOutbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout) + return directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout) } func (r *Router) matchRule( diff --git a/transport/wireguard/device_nat.go b/transport/wireguard/device_nat.go index e5a28c1b..d214b737 100644 --- a/transport/wireguard/device_nat.go +++ b/transport/wireguard/device_nat.go @@ -20,7 +20,7 @@ type natDeviceWrapper struct { ctx context.Context logger logger.ContextLogger packetOutbound chan *buf.Buffer - rewriter *ping.Rewriter + rewriter *ping.SourceRewriter buffer [][]byte } @@ -30,7 +30,7 @@ func NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Dev ctx: ctx, logger: logger, packetOutbound: make(chan *buf.Buffer, 256), - rewriter: ping.NewRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()), + rewriter: ping.NewSourceRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()), } return wrapper } From 12b055989b83e325c859b950ac24b39c4ad331ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 14 Sep 2025 17:28:43 +0800 Subject: [PATCH 026/185] Fix preConnectionCopy --- route/conn.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/route/conn.go b/route/conn.go index 82c8c2e9..16654704 100644 --- a/route/conn.go +++ b/route/conn.go @@ -235,7 +235,7 @@ func (m *ConnectionManager) preConnectionCopy(ctx context.Context, source net.Co err = m.connectionCopyEarlyWrite(source, destination, readHandshake, writeHandshake) if err == nil && N.NeedHandshakeForRead(source) { continue - } else if err == os.ErrInvalid || err == context.DeadlineExceeded { + } else if E.IsMulti(err, os.ErrInvalid, context.DeadlineExceeded, io.EOF) { err = nil } break @@ -340,10 +340,19 @@ func (m *ConnectionManager) connectionCopyEarlyWrite(source net.Conn, destinatio } return err } + var ( + isTimeout bool + isEOF bool + ) _, err = payload.ReadOnceFrom(source) - isTimeout := E.IsTimeout(err) - if err != nil && !(isTimeout || errors.Is(err, io.EOF)) { - return E.Cause(err, "read payload") + if err != nil { + if E.IsTimeout(err) { + isTimeout = true + } else if errors.Is(err, io.EOF) { + isEOF = true + } else { + return E.Cause(err, "read payload") + } } _ = source.SetReadDeadline(time.Time{}) if !payload.IsEmpty() || writeHandshake { @@ -354,6 +363,8 @@ func (m *ConnectionManager) connectionCopyEarlyWrite(source net.Conn, destinatio } if isTimeout { return context.DeadlineExceeded + } else if isEOF { + return io.EOF } return nil } From 7f3ea8dbd896c967eb94886c0edb0f06b0ab1256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 15 Sep 2025 19:52:28 +0800 Subject: [PATCH 027/185] Update WireGuard and Tailscale --- common/dialer/default.go | 14 ++------------ common/dialer/wireguard.go | 6 +----- go.mod | 2 +- go.sum | 4 ++-- protocol/wireguard/init.go | 10 ---------- service/derp/service.go | 7 ++++--- transport/wireguard/client_bind.go | 13 ++++++++----- transport/wireguard/endpoint.go | 5 +++-- 8 files changed, 21 insertions(+), 40 deletions(-) delete mode 100644 protocol/wireguard/init.go diff --git a/common/dialer/default.go b/common/dialer/default.go index 7eac3729..992b3a89 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -356,18 +356,8 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina return trackPacketConn(packetConn, nil) } -func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - udpListener := d.udpListener - udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error { - for _, wgControlFn := range WgControlFns { - err := wgControlFn(network, address, conn) - if err != nil { - return err - } - } - return nil - }) - return udpListener.ListenPacket(context.Background(), network, address) +func (d *DefaultDialer) WireGuardControl() control.Func { + return d.udpListener.Control } func trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/common/dialer/wireguard.go b/common/dialer/wireguard.go index fbd323d8..8a916a59 100644 --- a/common/dialer/wireguard.go +++ b/common/dialer/wireguard.go @@ -1,13 +1,9 @@ package dialer import ( - "net" - "github.com/sagernet/sing/common/control" ) type WireGuardListener interface { - ListenPacketCompat(network, address string) (net.PacketConn, error) + WireGuardControl() control.Func } - -var WgControlFns []control.Func diff --git a/go.mod b/go.mod index 2e21d23f..dd5d4e8c 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.8 - github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 + github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 github.com/sagernet/sing v0.8.0-beta.6 github.com/sagernet/sing-mux v0.3.4 diff --git a/go.sum b/go.sum index 62682a98..a97a8fc8 100644 --- a/go.sum +++ b/go.sum @@ -149,8 +149,8 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo= github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= -github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 h1:x/t3XqWshOlWqRuumpvbUvjtEr/6mJuBXAVovPefbUg= -github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= +github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o= +github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= diff --git a/protocol/wireguard/init.go b/protocol/wireguard/init.go deleted file mode 100644 index 848c113b..00000000 --- a/protocol/wireguard/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package wireguard - -import ( - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/wireguard-go/conn" -) - -func init() { - dialer.WgControlFns = conn.ControlFns -} diff --git a/service/derp/service.go b/service/derp/service.go index 686afc7c..049a5e4d 100644 --- a/service/derp/service.go +++ b/service/derp/service.go @@ -36,7 +36,7 @@ import ( aTLS "github.com/sagernet/sing/common/tls" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" - "github.com/sagernet/tailscale/client/tailscale" + "github.com/sagernet/tailscale/client/local" "github.com/sagernet/tailscale/derp" "github.com/sagernet/tailscale/derp/derphttp" "github.com/sagernet/tailscale/net/netmon" @@ -244,7 +244,7 @@ func (d *Service) Start(stage adapter.StartStage) error { } case adapter.StartStatePostStart: if len(d.verifyClientEndpoint) > 0 { - var endpoints []*tailscale.LocalClient + var endpoints []*local.Client endpointManager := service.FromContext[adapter.EndpointManager](d.ctx) for _, endpointTag := range d.verifyClientEndpoint { endpoint, loaded := endpointManager.Get(endpointTag) @@ -343,7 +343,8 @@ func (d *Service) startMeshWithHost(derpServer *derp.Server, server *option.DERP }) add := func(m derp.PeerPresentMessage) { derpServer.AddPacketForwarder(m.Key, meshClient) } remove := func(m derp.PeerGoneMessage) { derpServer.RemovePacketForwarder(m.Peer, meshClient) } - go meshClient.RunWatchConnectionLoop(context.Background(), derpServer.PublicKey(), logf, add, remove) + notifyError := func(err error) { d.logger.Error(err) } + go meshClient.RunWatchConnectionLoop(context.Background(), derpServer.PublicKey(), logf, add, remove, notifyError) return nil } diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index f1081855..54b7be86 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -162,7 +162,7 @@ func (c *ClientBind) SetMark(mark uint32) error { return nil } -func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint) error { +func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint, offset int) error { udpConn, err := c.connect() if err != nil { c.pauseManager.WaitActive() @@ -170,15 +170,18 @@ func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint) error { return err } destination := netip.AddrPort(ep.(remoteEndpoint)) - for _, b := range bufs { - if len(b) > 3 { + for _, buf := range bufs { + if offset > 0 { + buf = buf[offset:] + } + if len(buf) > 3 { reserved, loaded := c.reservedForEndpoint[destination] if !loaded { reserved = c.reserved } - copy(b[1:4], reserved[:]) + copy(buf[1:4], reserved[:]) } - _, err = udpConn.WriteToUDPAddrPort(b, destination) + _, err = udpConn.WriteToUDPAddrPort(buf, destination) if err != nil { udpConn.Close() return err diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index 12718b91..dac07c85 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -14,6 +14,7 @@ import ( "unsafe" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -153,9 +154,9 @@ func (e *Endpoint) Start(resolve bool) error { return nil } var bind conn.Bind - wgListener, isWgListener := common.Cast[conn.Listener](e.options.Dialer) + wgListener, isWgListener := common.Cast[dialer.WireGuardListener](e.options.Dialer) if isWgListener { - bind = conn.NewStdNetBind(wgListener) + bind = conn.NewStdNetBind(wgListener.WireGuardControl()) } else { var ( isConnect bool From ed1ee4c3a4d203d93079b10d21d224d769e5965c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 16 Sep 2025 00:58:54 +0800 Subject: [PATCH 028/185] Update quic-go to v0.55.0 --- cmd/sing-box/cmd_tools_fetch_http3.go | 2 +- dns/transport/quic/http3.go | 2 +- dns/transport/quic/quic.go | 8 ++++---- go.mod | 4 ++-- go.sum | 8 ++++---- test/box_test.go | 2 +- transport/v2rayquic/client.go | 6 +++--- transport/v2rayquic/server.go | 2 +- transport/v2rayquic/stream.go | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd/sing-box/cmd_tools_fetch_http3.go b/cmd/sing-box/cmd_tools_fetch_http3.go index b7a31a72..3caa1e88 100644 --- a/cmd/sing-box/cmd_tools_fetch_http3.go +++ b/cmd/sing-box/cmd_tools_fetch_http3.go @@ -22,7 +22,7 @@ func initializeHTTP3Client(instance *box.Box) error { } http3Client = &http.Client{ Transport: &http3.Transport{ - Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { destination := M.ParseSocksaddr(addr) udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination) if dErr != nil { diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index e81e6d15..0459d685 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -102,7 +102,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options destination: &destinationURL, headers: headers, transport: &http3.Transport{ - Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) { + Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (*quic.Conn, error) { conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, serverAddr) if dialErr != nil { return nil, dialErr diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index 39bbab8e..a54cddcb 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -38,7 +38,7 @@ type Transport struct { serverAddr M.Socksaddr tlsConfig tls.Config access sync.Mutex - connection quic.EarlyConnection + connection *quic.Conn } func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { @@ -88,7 +88,7 @@ func (t *Transport) Close() error { func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { var ( - conn quic.Connection + conn *quic.Conn err error response *mDNS.Msg ) @@ -110,7 +110,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, return nil, err } -func (t *Transport) openConnection() (quic.EarlyConnection, error) { +func (t *Transport) openConnection() (*quic.Conn, error) { connection := t.connection if connection != nil && !common.Done(connection.Context()) { return connection, nil @@ -139,7 +139,7 @@ func (t *Transport) openConnection() (quic.EarlyConnection, error) { return earlyConnection, nil } -func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.Connection) (*mDNS.Msg, error) { +func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.Conn) (*mDNS.Msg, error) { stream, err := conn.OpenStreamSync(ctx) if err != nil { return nil, err diff --git a/go.mod b/go.mod index dd5d4e8c..c7a16b73 100644 --- a/go.mod +++ b/go.mod @@ -26,10 +26,10 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 - github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 + github.com/sagernet/quic-go v0.55.0-sing-box-mod.2 github.com/sagernet/sing v0.8.0-beta.6 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.3 + github.com/sagernet/sing-quic v0.6.0-beta.4 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index a97a8fc8..635a7619 100644 --- a/go.sum +++ b/go.sum @@ -155,14 +155,14 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 h1:12pJN/zdpRltLG8l8JA65QYy/a+Mz938yAN3ZQUinbo= -github.com/sagernet/quic-go v0.54.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= +github.com/sagernet/quic-go v0.55.0-sing-box-mod.2 h1:I79gW4Xl5ciVARHfnp122lDAMhC0AwUCU765Q8Kxdfo= +github.com/sagernet/quic-go v0.55.0-sing-box-mod.2/go.mod h1:IE9naq7Kekj0rPAdWc0GLW1ENR7gAOQV9VRTDlKN8Bk= github.com/sagernet/sing v0.8.0-beta.6 h1:GXv1j1xWHihx6ptyOXh0yp4jUqJoNjCqD8d+AI9rnLU= github.com/sagernet/sing v0.8.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.3 h1:Z2vt49f9vNtHc9BbF9foI859n4+NAOV3gBeB1LuzL1Q= -github.com/sagernet/sing-quic v0.6.0-beta.3/go.mod h1:2/swrSS6wG6MyQA5Blq31VEWitHgBju+yZE8cPK1J5I= +github.com/sagernet/sing-quic v0.6.0-beta.4 h1:2k/+Xrv/pjl7AYC7LD9tcB7y1lIgw04LjJjqTI8q5Xk= +github.com/sagernet/sing-quic v0.6.0-beta.4/go.mod h1:FNvKPADzMZprwm7UQCcCGPhYifpb5rxoCOntOupJU+8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= diff --git a/test/box_test.go b/test/box_test.go index de2602e8..152948d2 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -89,7 +89,7 @@ func testQUIC(t *testing.T, clientPort uint16) { dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "") client := &http.Client{ Transport: &http3.RoundTripper{ - Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { destination := M.ParseSocksaddr(addr) udpConn, err := dialer.DialContext(ctx, N.NetworkUDP, destination) if err != nil { diff --git a/transport/v2rayquic/client.go b/transport/v2rayquic/client.go index 803d58c5..3e0d8b81 100644 --- a/transport/v2rayquic/client.go +++ b/transport/v2rayquic/client.go @@ -29,7 +29,7 @@ type Client struct { tlsConfig tls.Config quicConfig *quic.Config connAccess sync.Mutex - conn common.TypedValue[quic.Connection] + conn common.TypedValue[*quic.Conn] rawConn net.Conn } @@ -49,7 +49,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt }, nil } -func (c *Client) offer() (quic.Connection, error) { +func (c *Client) offer() (*quic.Conn, error) { conn := c.conn.Load() if conn != nil && !common.Done(conn.Context()) { return conn, nil @@ -67,7 +67,7 @@ func (c *Client) offer() (quic.Connection, error) { return conn, nil } -func (c *Client) offerNew() (quic.Connection, error) { +func (c *Client) offerNew() (*quic.Conn, error) { udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.serverAddr) if err != nil { return nil, err diff --git a/transport/v2rayquic/server.go b/transport/v2rayquic/server.go index 4c4397e6..bde6e87a 100644 --- a/transport/v2rayquic/server.go +++ b/transport/v2rayquic/server.go @@ -84,7 +84,7 @@ func (s *Server) acceptLoop() { } } -func (s *Server) streamAcceptLoop(conn quic.Connection) error { +func (s *Server) streamAcceptLoop(conn *quic.Conn) error { for { stream, err := conn.AcceptStream(s.ctx) if err != nil { diff --git a/transport/v2rayquic/stream.go b/transport/v2rayquic/stream.go index e268b38f..5ec3b7cb 100644 --- a/transport/v2rayquic/stream.go +++ b/transport/v2rayquic/stream.go @@ -8,8 +8,8 @@ import ( ) type StreamWrapper struct { - Conn quic.Connection - quic.Stream + Conn *quic.Conn + *quic.Stream } func (s *StreamWrapper) Read(p []byte) (n int, err error) { From 6b90b61358e1ca5b70805b3e26f6bf3d4816562f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Sep 2025 16:22:28 +0800 Subject: [PATCH 029/185] documentation: Update chinese translations --- docs/configuration/certificate/index.zh.md | 54 +++++++ docs/configuration/dns/server/dhcp.zh.md | 38 +++++ docs/configuration/dns/server/fakeip.zh.md | 35 +++++ docs/configuration/dns/server/hosts.zh.md | 96 +++++++++++++ docs/configuration/dns/server/http3.zh.md | 71 +++++++++ docs/configuration/dns/server/https.zh.md | 71 +++++++++ docs/configuration/dns/server/local.zh.md | 61 ++++++++ docs/configuration/dns/server/quic.zh.md | 58 ++++++++ docs/configuration/dns/server/resolved.zh.md | 83 +++++++++++ docs/configuration/dns/server/tailscale.zh.md | 83 +++++++++++ docs/configuration/dns/server/tcp.zh.md | 52 +++++++ docs/configuration/dns/server/tls.zh.md | 58 ++++++++ docs/configuration/dns/server/udp.zh.md | 52 +++++++ docs/configuration/endpoint/tailscale.zh.md | 103 +++++++++++++ docs/configuration/inbound/trojan.zh.md | 6 +- docs/configuration/service/derp.zh.md | 135 ++++++++++++++++++ docs/configuration/service/index.zh.md | 32 +++++ docs/configuration/service/resolved.zh.md | 44 ++++++ docs/configuration/service/ssm-api.zh.md | 58 ++++++++ docs/configuration/shared/udp-over-tcp.zh.md | 82 +++++++++++ 20 files changed, 1268 insertions(+), 4 deletions(-) create mode 100644 docs/configuration/certificate/index.zh.md create mode 100644 docs/configuration/dns/server/dhcp.zh.md create mode 100644 docs/configuration/dns/server/fakeip.zh.md create mode 100644 docs/configuration/dns/server/hosts.zh.md create mode 100644 docs/configuration/dns/server/http3.zh.md create mode 100644 docs/configuration/dns/server/https.zh.md create mode 100644 docs/configuration/dns/server/local.zh.md create mode 100644 docs/configuration/dns/server/quic.zh.md create mode 100644 docs/configuration/dns/server/resolved.zh.md create mode 100644 docs/configuration/dns/server/tailscale.zh.md create mode 100644 docs/configuration/dns/server/tcp.zh.md create mode 100644 docs/configuration/dns/server/tls.zh.md create mode 100644 docs/configuration/dns/server/udp.zh.md create mode 100644 docs/configuration/endpoint/tailscale.zh.md create mode 100644 docs/configuration/service/derp.zh.md create mode 100644 docs/configuration/service/index.zh.md create mode 100644 docs/configuration/service/resolved.zh.md create mode 100644 docs/configuration/service/ssm-api.zh.md create mode 100644 docs/configuration/shared/udp-over-tcp.zh.md diff --git a/docs/configuration/certificate/index.zh.md b/docs/configuration/certificate/index.zh.md new file mode 100644 index 00000000..9572e9cd --- /dev/null +++ b/docs/configuration/certificate/index.zh.md @@ -0,0 +1,54 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# 证书 + +### 结构 + +```json +{ + "store": "", + "certificate": [], + "certificate_path": [], + "certificate_directory_path": [] +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +### 字段 + +#### store + +默认的 X509 受信任 CA 证书列表。 + +| 类型 | 描述 | +|--------------------|--------------------------------------------------------------------------------------------| +| `system`(默认) | 系统受信任的 CA 证书 | +| `mozilla` | [Mozilla 包含列表](https://wiki.mozilla.org/CA/Included_Certificates)(已移除中国 CA 证书) | +| `none` | 空列表 | + +#### certificate + +要信任的证书行数组,PEM 格式。 + +#### certificate_path + +!!! note "" + + 文件修改时将自动重新加载。 + +要信任的证书路径,PEM 格式。 + +#### certificate_directory_path + +!!! note "" + + 文件修改时将自动重新加载。 + +搜索要信任的证书的目录路径,PEM 格式。 \ No newline at end of file diff --git a/docs/configuration/dns/server/dhcp.zh.md b/docs/configuration/dns/server/dhcp.zh.md new file mode 100644 index 00000000..2a67a7a3 --- /dev/null +++ b/docs/configuration/dns/server/dhcp.zh.md @@ -0,0 +1,38 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DHCP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "dhcp", + "tag": "", + + "interface": "", + + // 拨号字段 + } + ] + } +} +``` + +### 字段 + +#### interface + +要监听的网络接口名称。 + +默认使用默认接口。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/fakeip.zh.md b/docs/configuration/dns/server/fakeip.zh.md new file mode 100644 index 00000000..06dbdff0 --- /dev/null +++ b/docs/configuration/dns/server/fakeip.zh.md @@ -0,0 +1,35 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Fake IP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "fakeip", + "tag": "", + + "inet4_range": "198.18.0.0/15", + "inet6_range": "fc00::/18" + } + ] + } +} +``` + +### 字段 + +#### inet4_range + +FakeIP 的 IPv4 地址范围。 + +#### inet6_range + +FakeIP 的 IPv6 地址范围。 \ No newline at end of file diff --git a/docs/configuration/dns/server/hosts.zh.md b/docs/configuration/dns/server/hosts.zh.md new file mode 100644 index 00000000..43878f4e --- /dev/null +++ b/docs/configuration/dns/server/hosts.zh.md @@ -0,0 +1,96 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Hosts + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "hosts", + "tag": "", + + "path": [], + "predefined": {} + } + ] + } +} +``` + +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + +### 字段 + +#### path + +hosts 文件路径列表。 + +默认使用 `/etc/hosts`。 + +在 Windows 上默认使用 `C:\Windows\System32\Drivers\etc\hosts`。 + +示例: + +```json +{ + // "path": "/etc/hosts" + + "path": [ + "/etc/hosts", + "$HOME/.hosts" + ] +} +``` + +#### predefined + +预定义的 hosts。 + +示例: + +```json +{ + "predefined": { + "www.google.com": "127.0.0.1", + "localhost": [ + "127.0.0.1", + "::1" + ] + } +} +``` + +### 示例 + +=== "如果可用则使用 hosts" + + ```json + { + "dns": { + "servers": [ + { + ... + }, + { + "type": "hosts", + "tag": "hosts" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "hosts" + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/http3.zh.md b/docs/configuration/dns/server/http3.zh.md new file mode 100644 index 00000000..70e13b10 --- /dev/null +++ b/docs/configuration/dns/server/http3.zh.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over HTTP3 (DoH3) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "h3", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 H3 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `443`。 + +#### path + +DNS 服务器的路径。 + +默认使用 `/dns-query`。 + +#### headers + +发送到 DNS 服务器的额外标头。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/https.zh.md b/docs/configuration/dns/server/https.zh.md new file mode 100644 index 00000000..691d5eb5 --- /dev/null +++ b/docs/configuration/dns/server/https.zh.md @@ -0,0 +1,71 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over HTTPS (DoH) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "https", + "tag": "", + + "server": "", + "server_port": 443, + + "path": "", + "headers": {}, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 HTTPS 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `443`。 + +#### path + +DNS 服务器的路径。 + +默认使用 `/dns-query`。 + +#### headers + +发送到 DNS 服务器的额外标头。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/local.zh.md b/docs/configuration/dns/server/local.zh.md new file mode 100644 index 00000000..50ac05ac --- /dev/null +++ b/docs/configuration/dns/server/local.zh.md @@ -0,0 +1,61 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [prefer_go](#prefer_go) + +!!! question "自 sing-box 1.12.0 起" + +# Local + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "local", + "tag": "", + "prefer_go": false, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版本地服务器的区别" + + * 旧的传统本地服务器只处理 IP 请求;新的服务器处理所有类型的请求,并支持 IP 请求的并发处理。 + * 旧的本地服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + +### 字段 + +#### prefer_go + +!!! question "自 sing-box 1.13.0 起" + +启用后,`local` DNS 服务器将尽可能通过拨号自身来解析 DNS。 + +具体来说,它禁用了在 sing-box 1.13.0 中作为功能添加的以下行为: + +1. 在 Apple 平台上:尝试在 NetworkExtension 中使用 `getaddrinfo` 解析 A/AAAA 请求。 +2. 在 Linux 上:当可用时通过 `systemd-resolvd` 的 DBus 接口进行解析。 + +作为唯一的例外,它无法禁用以下行为: + +1. 在 Android 图形客户端中, +`local` 将始终通过平台接口解析 DNS, +因为没有其他方法来获取上游 DNS 服务器; +在运行 Android 10 以下版本的设备上,此接口只能解析 A/AAAA 请求。 + +2. 在 macOS 上,`local` 会在 Network Extension 中首先尝试 DHCP,由于 DHCP 遵循拨号字段, +它不会被 `prefer_go` 禁用。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/quic.zh.md b/docs/configuration/dns/server/quic.zh.md new file mode 100644 index 00000000..03b3002c --- /dev/null +++ b/docs/configuration/dns/server/quic.zh.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over QUIC (DoQ) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "quic", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 QUIC 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `853`。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/resolved.zh.md b/docs/configuration/dns/server/resolved.zh.md new file mode 100644 index 00000000..d59f8384 --- /dev/null +++ b/docs/configuration/dns/server/resolved.zh.md @@ -0,0 +1,83 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Resolved + +```json +{ + "dns": { + "servers": [ + { + "type": "resolved", + "tag": "", + + "service": "resolved", + "accept_default_resolvers": false + } + ] + } +} +``` + +### 字段 + +#### service + +==必填== + +[Resolved 服务](/zh/configuration/service/resolved) 的标签。 + +#### accept_default_resolvers + +指示是否除了匹配域名外,还应接受默认 DNS 解析器以进行回退查询。 + +具体来说,默认 DNS 解析器是设置了 `SetLinkDefaultRoute` 或 `SetLinkDomains ~.` 的 DNS 服务器。 + +如果未启用,对于不匹配搜索域或匹配域的请求,将返回 `NXDOMAIN`。 + +### 示例 + +=== "仅分割 DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "resolved", + "tag": "resolved", + "service": "resolved" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "resolved" + } + ] + } + } + ``` + +=== "用作全局 DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "resolved", + "service": "resolved", + "accept_default_resolvers": true + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/tailscale.zh.md b/docs/configuration/dns/server/tailscale.zh.md new file mode 100644 index 00000000..5cb20776 --- /dev/null +++ b/docs/configuration/dns/server/tailscale.zh.md @@ -0,0 +1,83 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Tailscale + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "tailscale", + "tag": "", + + "endpoint": "ts-ep", + "accept_default_resolvers": false + } + ] + } +} +``` + +### 字段 + +#### endpoint + +==必填== + +[Tailscale 端点](/zh/configuration/endpoint/tailscale) 的标签。 + +#### accept_default_resolvers + +指示是否除了 MagicDNS 外,还应接受默认 DNS 解析器以进行回退查询。 + +如果未启用,对于非 Tailscale 域名查询将返回 `NXDOMAIN`。 + +### 示例 + +=== "仅 MagicDNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "local", + "tag": "local" + }, + { + "type": "tailscale", + "tag": "ts", + "endpoint": "ts-ep" + } + ], + "rules": [ + { + "ip_accept_any": true, + "server": "ts" + } + ] + } + } + ``` + +=== "用作全局 DNS" + + ```json + { + "dns": { + "servers": [ + { + "type": "tailscale", + "endpoint": "ts-ep", + "accept_default_resolvers": true + } + ] + } + } + ``` \ No newline at end of file diff --git a/docs/configuration/dns/server/tcp.zh.md b/docs/configuration/dns/server/tcp.zh.md new file mode 100644 index 00000000..6f439bdf --- /dev/null +++ b/docs/configuration/dns/server/tcp.zh.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# TCP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "tcp", + "tag": "", + + "server": "", + "server_port": 53, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 TCP 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `53`。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/tls.zh.md b/docs/configuration/dns/server/tls.zh.md new file mode 100644 index 00000000..7402e521 --- /dev/null +++ b/docs/configuration/dns/server/tls.zh.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DNS over TLS (DoT) + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "tls", + "tag": "", + + "server": "", + "server_port": 853, + + "tls": {}, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 TLS 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `853`。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/dns/server/udp.zh.md b/docs/configuration/dns/server/udp.zh.md new file mode 100644 index 00000000..63feedd8 --- /dev/null +++ b/docs/configuration/dns/server/udp.zh.md @@ -0,0 +1,52 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# UDP + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "udp", + "tag": "", + + "server": "", + "server_port": 53, + + // 拨号字段 + } + ] + } +} +``` + +!!! info "与旧版 UDP 服务器的区别" + + * 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。 + * 旧服务器使用 `address_resolver` 和 `address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver` 和 `domain_strategy`。 + +### 字段 + +#### server + +==必填== + +DNS 服务器的地址。 + +如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。 + +#### server_port + +DNS 服务器的端口。 + +默认使用 `53`。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/endpoint/tailscale.zh.md b/docs/configuration/endpoint/tailscale.zh.md new file mode 100644 index 00000000..395ecbde --- /dev/null +++ b/docs/configuration/endpoint/tailscale.zh.md @@ -0,0 +1,103 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +### 结构 + +```json +{ + "type": "tailscale", + "tag": "ts-ep", + "state_directory": "", + "auth_key": "", + "control_url": "", + "ephemeral": false, + "hostname": "", + "accept_routes": false, + "exit_node": "", + "exit_node_allow_lan_access": false, + "advertise_routes": [], + "advertise_exit_node": false, + "udp_timeout": "5m", + + ... // 拨号字段 +} +``` + +### 字段 + +#### state_directory + +存储 Tailscale 状态的目录。 + +默认使用 `tailscale`。 + +示例:`$HOME/.tailscale` + +#### auth_key + +!!! note + + 认证密钥不是必需的。默认情况下,sing-box 将记录登录 URL(或在图形客户端上弹出通知)。 + +用于创建节点的认证密钥。如果节点已经创建(从之前存储的状态),则不使用此字段。 + +#### control_url + +协调服务器 URL。 + +默认使用 `https://controlplane.tailscale.com`。 + +#### ephemeral + +指示实例是否应注册为临时节点 (https://tailscale.com/s/ephemeral-nodes)。 + +#### hostname + +节点的主机名。 + +默认使用系统主机名。 + +示例:`localhost` + +#### accept_routes + +指示节点是否应接受其他节点通告的路由。 + +#### exit_node + +要使用的出口节点名称或 IP 地址。 + +#### exit_node_allow_lan_access + +!!! note + + 当出口节点没有相应的通告路由时,即使设置了 `exit_node_allow_lan_access`,私有流量也无法路由到出口节点。 + +指示本地可访问的子网应该直接路由还是通过出口节点路由。 + +#### advertise_routes + +通告到 Tailscale 网络的 CIDR 前缀,作为可通过当前节点访问的路由。 + +示例:`["192.168.1.1/24"]` + +#### advertise_exit_node + +指示节点是否应将自己通告为出口节点。 + +#### udp_timeout + +UDP NAT 过期时间。 + +默认使用 `5m`。 + +### 拨号字段 + +!!! note + + Tailscale 端点中的拨号字段仅控制它如何连接到控制平面,与实际连接无关。 + +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file diff --git a/docs/configuration/inbound/trojan.zh.md b/docs/configuration/inbound/trojan.zh.md index d8b30cae..fa86d613 100644 --- a/docs/configuration/inbound/trojan.zh.md +++ b/docs/configuration/inbound/trojan.zh.md @@ -43,13 +43,11 @@ Trojan 用户。 #### tls -==如果启用 HTTP3 则必填== - -TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 #### fallback -!!! quote "" +!!! failure "" 没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。 diff --git a/docs/configuration/service/derp.zh.md b/docs/configuration/service/derp.zh.md new file mode 100644 index 00000000..ab89ac08 --- /dev/null +++ b/docs/configuration/service/derp.zh.md @@ -0,0 +1,135 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# DERP + +DERP 服务是一个 Tailscale DERP 服务器,类似于 [derper](https://pkg.go.dev/tailscale.com/cmd/derper)。 + +### 结构 + +```json +{ + "type": "derp", + + ... // 监听字段 + + "tls": {}, + "config_path": "", + "verify_client_endpoint": [], + "verify_client_url": [], + "home": "", + "mesh_with": [], + "mesh_psk": "", + "mesh_psk_file": "", + "stun": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 + +#### config_path + +==必填== + +Derper 配置文件路径。 + +示例:`derper.key` + +#### verify_client_endpoint + +用于验证客户端的 Tailscale 端点标签。 + +#### verify_client_url + +用于验证客户端的 URL。 + +对象格式: + +```json +{ + "url": "https://my-headscale.com/verify", + + ... // 拨号字段 +} +``` + +将数组值设置为字符串 `__URL__` 等同于配置: + +```json +{ "url": __URL__ } +``` + +#### home + +在根路径提供的内容。可以留空(默认值,显示默认主页)、`blank` 显示空白页面,或一个重定向的 URL。 + +#### mesh_with + +与其他 DERP 服务器组网。 + +对象格式: + +```json +{ + "server": "", + "server_port": "", + "host": "", + "tls": {}, + + ... // 拨号字段 +} +``` + +对象字段: + +- `server`:**必填** DERP 服务器地址。 +- `server_port`:**必填** DERP 服务器端口。 +- `host`:自定义 DERP 主机名。 +- `tls`:[TLS](/zh/configuration/shared/tls/#outbound) +- `拨号字段`:[拨号字段](/zh/configuration/shared/dial/) + +#### mesh_psk + +DERP 组网的预共享密钥。 + +#### mesh_psk_file + +DERP 组网的预共享密钥文件。 + +#### stun + +STUN 服务器监听选项。 + +对象格式: + +```json +{ + "enabled": true, + + ... // 监听字段 +} +``` + +对象字段: + +- `enabled`:**必填** 启用 STUN 服务器。 +- `listen`:**必填** STUN 服务器监听地址,默认为 `::`。 +- `listen_port`:**必填** STUN 服务器监听端口,默认为 `3478`。 +- `其他监听字段`:[监听字段](/zh/configuration/shared/listen/) + +将 `stun` 值设置为数字 `__PORT__` 等同于配置: + +```json +{ "enabled": true, "listen_port": __PORT__ } +``` \ No newline at end of file diff --git a/docs/configuration/service/index.zh.md b/docs/configuration/service/index.zh.md new file mode 100644 index 00000000..d534aa85 --- /dev/null +++ b/docs/configuration/service/index.zh.md @@ -0,0 +1,32 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# 服务 + +### 结构 + +```json +{ + "services": [ + { + "type": "", + "tag": "" + } + ] +} +``` + +### 字段 + +| 类型 | 格式 | +|-----------|------------------------| +| `derp` | [DERP](./derp) | +| `resolved`| [Resolved](./resolved) | +| `ssm-api` | [SSM API](./ssm-api) | + +#### tag + +端点的标签。 \ No newline at end of file diff --git a/docs/configuration/service/resolved.zh.md b/docs/configuration/service/resolved.zh.md new file mode 100644 index 00000000..b8af4e95 --- /dev/null +++ b/docs/configuration/service/resolved.zh.md @@ -0,0 +1,44 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# Resolved + +Resolved 服务是一个伪造的 systemd-resolved DBUS 服务,用于从其他程序 +(如 NetworkManager)接收 DNS 设置并提供 DNS 解析。 + +另请参阅:[Resolved DNS 服务器](/zh/configuration/dns/server/resolved/) + +### 结构 + +```json +{ + "type": "resolved", + + ... // 监听字段 +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### listen + +==必填== + +监听地址。 + +默认使用 `127.0.0.53`。 + +#### listen_port + +==必填== + +监听端口。 + +默认使用 `53`。 \ No newline at end of file diff --git a/docs/configuration/service/ssm-api.zh.md b/docs/configuration/service/ssm-api.zh.md new file mode 100644 index 00000000..66e3e922 --- /dev/null +++ b/docs/configuration/service/ssm-api.zh.md @@ -0,0 +1,58 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.12.0 起" + +# SSM API + +SSM API 服务是一个用于管理 Shadowsocks 服务器的 RESTful API 服务器。 + +参阅 https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md + +### 结构 + +```json +{ + "type": "ssm-api", + + ... // 监听字段 + + "servers": {}, + "cache_path": "", + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### servers + +==必填== + +从 HTTP 端点到 [Shadowsocks 入站](/zh/configuration/inbound/shadowsocks) 标签的映射对象。 + +选定的 Shadowsocks 入站必须配置启用 [managed](/zh/configuration/inbound/shadowsocks#managed)。 + +示例: + +```json +{ + "servers": { + "/": "ss-in" + } +} +``` + +#### cache_path + +如果设置,当服务器即将停止时,流量和用户状态将保存到指定的 JSON 文件中, +以便在下次启动时恢复。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 \ No newline at end of file diff --git a/docs/configuration/shared/udp-over-tcp.zh.md b/docs/configuration/shared/udp-over-tcp.zh.md new file mode 100644 index 00000000..fec9645e --- /dev/null +++ b/docs/configuration/shared/udp-over-tcp.zh.md @@ -0,0 +1,82 @@ +!!! warning "" + + 这是 SagerNet 创建的专有协议,不是 shadowsocks 的一部分。 + +UDP over TCP 协议用于在 TCP 中传输 UDP 数据包。 + +### 结构 + +```json +{ + "enabled": true, + "version": 2 +} +``` + +!!! info "" + + 当不指定版本时,结构可以用布尔值替换。 + +### 字段 + +#### enabled + +启用 UDP over TCP 协议。 + +#### version + +协议版本,`1` 或 `2`。 + +默认使用 2。 + +### 应用程序支持 + +| 项目 | UoT v1 | UoT v2 | +|--------------|----------------------|----------------------| +| sing-box | v0 (2022/08/11) | v1.2-beta9 | +| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) | +| Shadowrocket | v2.2.12 (2022/08/13) | / | + +### 协议详情 + +#### 协议版本 1 + +客户端向上层代理协议请求魔法地址以表示请求:`sp.udp-over-tcp.arpa` + +#### 流格式 + +| ATYP | 地址 | 端口 | 长度 | 数据 | +|------|----------|-------|--------|----------| +| u8 | 可变长 | u16be | u16be | 可变长 | + +**ATYP / 地址 / 端口**:使用 SOCKS 地址格式,但使用不同的地址类型: + +| ATYP | 地址类型 | +|--------|-----------| +| `0x00` | IPv4 地址 | +| `0x01` | IPv6 地址 | +| `0x02` | 域名 | + +#### 协议版本 2 + +协议版本 2 使用新的魔法地址:`sp.v2.udp-over-tcp.arpa` + +##### 请求格式 + +| isConnect | ATYP | 地址 | 端口 | +|-----------|------|----------|-------| +| u8 | u8 | 可变长 | u16be | + +**isConnect**:设置为 1 表示流使用连接格式,0 表示禁用。 + +**ATYP / 地址 / 端口**:请求目标,使用 SOCKS 地址格式。 + +##### 连接流格式 + +| 长度 | 数据 | +|--------|----------| +| u16be | 可变长 | + +##### 非连接流格式 + +与协议版本 1 中的流格式相同。 \ No newline at end of file From cb4deb0c2013a703c1426b5c8b7df827f6567d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Sep 2025 17:49:58 +0800 Subject: [PATCH 030/185] Do not use linkname by default to simplify debugging --- .github/workflows/build.yml | 2 +- .github/workflows/linux.yml | 2 +- Dockerfile | 2 +- Makefile | 2 +- cmd/internal/build_libbox/main.go | 4 +--- common/badtls/raw_conn.go | 2 +- common/badtls/raw_half_conn.go | 2 +- common/badtls/read_wait.go | 2 +- common/badtls/read_wait_stub.go | 2 +- common/badtls/registry.go | 2 +- common/badtls/registry_utls.go | 2 +- common/ktls/ktls.go | 2 +- common/ktls/ktls_alert.go | 2 +- common/ktls/ktls_cipher_suites_linux.go | 2 +- common/ktls/ktls_close.go | 2 +- common/ktls/ktls_const.go | 2 +- common/ktls/ktls_handshake_messages.go | 2 +- common/ktls/ktls_key_update.go | 2 +- common/ktls/ktls_linux.go | 2 +- common/ktls/ktls_prf.go | 2 +- common/ktls/ktls_read.go | 2 +- common/ktls/ktls_read_wait.go | 2 +- common/ktls/ktls_stub_nolinkname.go | 15 +++++++++++++++ .../ktls/{ktls_stub.go => ktls_stub_nonlinux.go} | 6 +++--- common/ktls/ktls_stub_oldgo.go | 15 +++++++++++++++ common/ktls/ktls_write.go | 2 +- release/local/debug.sh | 2 +- release/local/install.sh | 2 +- release/local/reinstall.sh | 2 +- 29 files changed, 59 insertions(+), 31 deletions(-) create mode 100644 common/ktls/ktls_stub_nolinkname.go rename common/ktls/{ktls_stub.go => ktls_stub_nonlinux.go} (67%) create mode 100644 common/ktls/ktls_stub_oldgo.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d637a6c0..9819bc62 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -146,7 +146,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build if: matrix.os != 'android' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f422e4ec..841edc10 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -85,7 +85,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build run: | diff --git a/Dockerfile b/Dockerfile index dd749bc4..62dba598 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN set -ex \ && export COMMIT=$(git rev-parse --short HEAD) \ && export VERSION=$(go run ./cmd/internal/read_tag) \ && go build -v -trimpath -tags \ - "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \ + "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname" \ -o /go/bin/sing-box \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box diff --git a/Makefile b/Makefile index b92bc6b6..39e34021 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale +TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 483bebed..974769d9 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -62,7 +62,7 @@ func init() { sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0") - sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") + sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack", "badlinkname") macOSTags = append(macOSTags, "with_dhcp") memcTags = append(memcTags, "with_tailscale") notMemcTags = append(notMemcTags, "with_low_memory") @@ -107,10 +107,8 @@ func buildAndroid() { } if !debugEnabled { - // sharedFlags[3] = sharedFlags[3] + " -checklinkname=0" args = append(args, sharedFlags...) } else { - // debugFlags[1] = debugFlags[1] + " -checklinkname=0" args = append(args, debugFlags...) } diff --git a/common/badtls/raw_conn.go b/common/badtls/raw_conn.go index 1029d66b..774e39a5 100644 --- a/common/badtls/raw_conn.go +++ b/common/badtls/raw_conn.go @@ -1,4 +1,4 @@ -//go:build go1.25 && !without_badtls +//go:build go1.25 && badlinkname package badtls diff --git a/common/badtls/raw_half_conn.go b/common/badtls/raw_half_conn.go index dd5e249e..4d2c8b64 100644 --- a/common/badtls/raw_half_conn.go +++ b/common/badtls/raw_half_conn.go @@ -1,4 +1,4 @@ -//go:build go1.25 && !without_badtls +//go:build go1.25 && badlinkname package badtls diff --git a/common/badtls/read_wait.go b/common/badtls/read_wait.go index d18b4c0e..8448b1a2 100644 --- a/common/badtls/read_wait.go +++ b/common/badtls/read_wait.go @@ -1,4 +1,4 @@ -//go:build go1.25 && !without_badtls +//go:build go1.25 && badlinkname package badtls diff --git a/common/badtls/read_wait_stub.go b/common/badtls/read_wait_stub.go index 4bd4fc60..9258a46e 100644 --- a/common/badtls/read_wait_stub.go +++ b/common/badtls/read_wait_stub.go @@ -1,4 +1,4 @@ -//go:build !go1.25 || without_badtls +//go:build !go1.25 || !badlinkname package badtls diff --git a/common/badtls/registry.go b/common/badtls/registry.go index cc11a16e..34cfe9ec 100644 --- a/common/badtls/registry.go +++ b/common/badtls/registry.go @@ -1,4 +1,4 @@ -//go:build go1.25 && !without_badtls +//go:build go1.25 && badlinkname package badtls diff --git a/common/badtls/registry_utls.go b/common/badtls/registry_utls.go index c0454355..330f64f5 100644 --- a/common/badtls/registry_utls.go +++ b/common/badtls/registry_utls.go @@ -1,4 +1,4 @@ -//go:build go1.25 && !without_badtls +//go:build go1.25 && badlinkname package badtls diff --git a/common/ktls/ktls.go b/common/ktls/ktls.go index eb2e86cf..33a59b13 100644 --- a/common/ktls/ktls.go +++ b/common/ktls/ktls.go @@ -1,4 +1,4 @@ -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_alert.go b/common/ktls/ktls_alert.go index 8c68d36b..e755feae 100644 --- a/common/ktls/ktls_alert.go +++ b/common/ktls/ktls_alert.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_cipher_suites_linux.go b/common/ktls/ktls_cipher_suites_linux.go index 348b1c42..571f0251 100644 --- a/common/ktls/ktls_cipher_suites_linux.go +++ b/common/ktls/ktls_cipher_suites_linux.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_close.go b/common/ktls/ktls_close.go index f9392a56..2052524d 100644 --- a/common/ktls/ktls_close.go +++ b/common/ktls/ktls_close.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_const.go b/common/ktls/ktls_const.go index 0b8e72e8..40cff760 100644 --- a/common/ktls/ktls_const.go +++ b/common/ktls/ktls_const.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_handshake_messages.go b/common/ktls/ktls_handshake_messages.go index e80531e1..f44958c0 100644 --- a/common/ktls/ktls_handshake_messages.go +++ b/common/ktls/ktls_handshake_messages.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_key_update.go b/common/ktls/ktls_key_update.go index 9e0d0ee1..35268e8f 100644 --- a/common/ktls/ktls_key_update.go +++ b/common/ktls/ktls_key_update.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_linux.go b/common/ktls/ktls_linux.go index bc9fb8b9..1e327751 100644 --- a/common/ktls/ktls_linux.go +++ b/common/ktls/ktls_linux.go @@ -1,4 +1,4 @@ -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_prf.go b/common/ktls/ktls_prf.go index f74a4876..ecf0b735 100644 --- a/common/ktls/ktls_prf.go +++ b/common/ktls/ktls_prf.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_read.go b/common/ktls/ktls_read.go index 45350441..7ffa1e18 100644 --- a/common/ktls/ktls_read.go +++ b/common/ktls/ktls_read.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_read_wait.go b/common/ktls/ktls_read_wait.go index 8c1a8ff0..4b4edc1e 100644 --- a/common/ktls/ktls_read_wait.go +++ b/common/ktls/ktls_read_wait.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/common/ktls/ktls_stub_nolinkname.go b/common/ktls/ktls_stub_nolinkname.go new file mode 100644 index 00000000..44a0b30c --- /dev/null +++ b/common/ktls/ktls_stub_nolinkname.go @@ -0,0 +1,15 @@ +//go:build linux && go1.25 && !badlinkname + +package ktls + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + return nil, E.New("kTLS requires build flags `badlinkname` and `-ldflags=-checklinkname=0`, please recompile your binary") +} diff --git a/common/ktls/ktls_stub.go b/common/ktls/ktls_stub_nonlinux.go similarity index 67% rename from common/ktls/ktls_stub.go rename to common/ktls/ktls_stub_nonlinux.go index 66d3e88a..e754b775 100644 --- a/common/ktls/ktls_stub.go +++ b/common/ktls/ktls_stub_nonlinux.go @@ -1,15 +1,15 @@ -//go:build !linux || !go1.25 || without_badtls +//go:build !linux package ktls import ( "context" - "os" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" aTLS "github.com/sagernet/sing/common/tls" ) func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { - return nil, os.ErrInvalid + return nil, E.New("kTLS is only supported on Linux") } diff --git a/common/ktls/ktls_stub_oldgo.go b/common/ktls/ktls_stub_oldgo.go new file mode 100644 index 00000000..613bf7f1 --- /dev/null +++ b/common/ktls/ktls_stub_oldgo.go @@ -0,0 +1,15 @@ +//go:build linux && !go1.25 + +package ktls + +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + aTLS "github.com/sagernet/sing/common/tls" +) + +func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) { + return nil, E.New("kTLS requires Go 1.25 or later, please recompile your binary") +} diff --git a/common/ktls/ktls_write.go b/common/ktls/ktls_write.go index 6f04ca29..76533b4a 100644 --- a/common/ktls/ktls_write.go +++ b/common/ktls/ktls_write.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && go1.25 && !without_badtls +//go:build linux && go1.25 && badlinkname package ktls diff --git a/release/local/debug.sh b/release/local/debug.sh index d649bed4..d6bd3057 100755 --- a/release/local/debug.sh +++ b/release/local/debug.sh @@ -13,7 +13,7 @@ pushd $PROJECT git fetch git reset FETCH_HEAD --hard git clean -fdx -go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_acme,debug ./cmd/sing-box +go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_acme,debug ./cmd/sing-box popd sudo systemctl stop sing-box diff --git a/release/local/install.sh b/release/local/install.sh index 3aa3d976..24e9d006 100755 --- a/release/local/install.sh +++ b/release/local/install.sh @@ -10,7 +10,7 @@ DIR=$(dirname "$0") PROJECT=$DIR/../.. pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box +go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box popd sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ diff --git a/release/local/reinstall.sh b/release/local/reinstall.sh index 04cef16b..71d07109 100755 --- a/release/local/reinstall.sh +++ b/release/local/reinstall.sh @@ -10,7 +10,7 @@ DIR=$(dirname "$0") PROJECT=$DIR/../.. pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box +go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box popd sudo systemctl stop sing-box From 9ac0539ffde4787bba5e2bb3fe4289d2a294b392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Sep 2025 18:12:51 +0800 Subject: [PATCH 031/185] Remove compatibility codes --- common/dialer/default.go | 27 ++++++++++----------------- common/dialer/default_go1.20.go | 19 ------------------- common/dialer/default_go1.21.go | 11 ----------- common/dialer/default_nongo1.20.go | 22 ---------------------- common/dialer/default_nongo1.21.go | 12 ------------ common/dialer/tfo.go | 4 +--- common/dialer/tfo_stub.go | 20 -------------------- common/listener/listener_go121.go | 11 ----------- common/listener/listener_go123.go | 16 ---------------- common/listener/listener_nongo121.go | 10 ---------- common/listener/listener_nongo123.go | 15 --------------- common/listener/listener_tcp.go | 11 ++++++----- common/tls/ech_stub.go | 23 ----------------------- 13 files changed, 17 insertions(+), 184 deletions(-) delete mode 100644 common/dialer/default_go1.20.go delete mode 100644 common/dialer/default_go1.21.go delete mode 100644 common/dialer/default_nongo1.20.go delete mode 100644 common/dialer/default_nongo1.21.go delete mode 100644 common/dialer/tfo_stub.go delete mode 100644 common/listener/listener_go121.go delete mode 100644 common/listener/listener_go123.go delete mode 100644 common/listener/listener_nongo121.go delete mode 100644 common/listener/listener_nongo123.go delete mode 100644 common/tls/ech_stub.go diff --git a/common/dialer/default.go b/common/dialer/default.go index 992b3a89..f3481439 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -20,6 +20,8 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" + + "github.com/metacubex/tfo-go" ) var ( @@ -28,8 +30,8 @@ var ( ) type DefaultDialer struct { - dialer4 tcpDialer - dialer6 tcpDialer + dialer4 tfo.Dialer + dialer6 tfo.Dialer udpDialer4 net.Dialer udpDialer6 net.Dialer udpListener net.ListenConfig @@ -177,19 +179,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String() } if options.TCPMultiPath { - if !go121Available { - return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") - } - setMultiPathTCP(&dialer4) - } - tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) - if err != nil { - return nil, err - } - tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen) - if err != nil { - return nil, err + dialer4.SetMultipathTCP(true) } + tcpDialer4 := tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen} + tcpDialer6 := tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen} return &DefaultDialer{ dialer4: tcpDialer4, dialer6: tcpDialer6, @@ -269,7 +262,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin } var dialer net.Dialer if N.NetworkName(network) == N.NetworkTCP { - dialer = dialerFromTCPDialer(d.dialer4) + dialer = d.dialer4.Dialer } else { dialer = d.udpDialer4 } @@ -317,9 +310,9 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer { if !destination.Is6() { - return dialerFromTCPDialer(d.dialer6) + return d.dialer6.Dialer } else { - return dialerFromTCPDialer(d.dialer4) + return d.dialer4.Dialer } } diff --git a/common/dialer/default_go1.20.go b/common/dialer/default_go1.20.go deleted file mode 100644 index 9dde955f..00000000 --- a/common/dialer/default_go1.20.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build go1.20 - -package dialer - -import ( - "net" - - "github.com/metacubex/tfo-go" -) - -type tcpDialer = tfo.Dialer - -func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { - return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil -} - -func dialerFromTCPDialer(dialer tcpDialer) net.Dialer { - return dialer.Dialer -} diff --git a/common/dialer/default_go1.21.go b/common/dialer/default_go1.21.go deleted file mode 100644 index 6ecb5b25..00000000 --- a/common/dialer/default_go1.21.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build go1.21 - -package dialer - -import "net" - -const go121Available = true - -func setMultiPathTCP(dialer *net.Dialer) { - dialer.SetMultipathTCP(true) -} diff --git a/common/dialer/default_nongo1.20.go b/common/dialer/default_nongo1.20.go deleted file mode 100644 index b2e4638d..00000000 --- a/common/dialer/default_nongo1.20.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build !go1.20 - -package dialer - -import ( - "net" - - E "github.com/sagernet/sing/common/exceptions" -) - -type tcpDialer = net.Dialer - -func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { - if tfoEnabled { - return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.") - } - return dialer, nil -} - -func dialerFromTCPDialer(dialer tcpDialer) net.Dialer { - return dialer -} diff --git a/common/dialer/default_nongo1.21.go b/common/dialer/default_nongo1.21.go deleted file mode 100644 index 386d50dd..00000000 --- a/common/dialer/default_nongo1.21.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !go1.21 - -package dialer - -import ( - "net" -) - -const go121Available = false - -func setMultiPathTCP(dialer *net.Dialer) { -} diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index 4832c12d..ae04e71f 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -1,5 +1,3 @@ -//go:build go1.20 - package dialer import ( @@ -32,7 +30,7 @@ type slowOpenConn struct { err error } -func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP { switch N.NetworkName(network) { case N.NetworkTCP, N.NetworkUDP: diff --git a/common/dialer/tfo_stub.go b/common/dialer/tfo_stub.go deleted file mode 100644 index 144902e5..00000000 --- a/common/dialer/tfo_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !go1.20 - -package dialer - -import ( - "context" - "net" - - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - switch N.NetworkName(network) { - case N.NetworkTCP, N.NetworkUDP: - return dialer.DialContext(ctx, network, destination.String()) - default: - return dialer.DialContext(ctx, network, destination.AddrString()) - } -} diff --git a/common/listener/listener_go121.go b/common/listener/listener_go121.go deleted file mode 100644 index 5af1b05a..00000000 --- a/common/listener/listener_go121.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build go1.21 - -package listener - -import "net" - -const go121Available = true - -func setMultiPathTCP(listenConfig *net.ListenConfig) { - listenConfig.SetMultipathTCP(true) -} diff --git a/common/listener/listener_go123.go b/common/listener/listener_go123.go deleted file mode 100644 index 2e1f4cf4..00000000 --- a/common/listener/listener_go123.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build go1.23 - -package listener - -import ( - "net" - "time" -) - -func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) { - listener.KeepAliveConfig = net.KeepAliveConfig{ - Enable: true, - Idle: idle, - Interval: interval, - } -} diff --git a/common/listener/listener_nongo121.go b/common/listener/listener_nongo121.go deleted file mode 100644 index 36073afe..00000000 --- a/common/listener/listener_nongo121.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !go1.21 - -package listener - -import "net" - -const go121Available = false - -func setMultiPathTCP(listenConfig *net.ListenConfig) { -} diff --git a/common/listener/listener_nongo123.go b/common/listener/listener_nongo123.go deleted file mode 100644 index e1582981..00000000 --- a/common/listener/listener_nongo123.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !go1.23 - -package listener - -import ( - "net" - "time" - - "github.com/sagernet/sing/common/control" -) - -func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) { - listener.KeepAlive = idle - listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval)) -} diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index 53d7bc86..7ac7bbba 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -46,13 +46,14 @@ func (l *Listener) ListenTCP() (net.Listener, error) { if keepInterval == 0 { keepInterval = C.TCPKeepAliveInterval } - setKeepAliveConfig(&listenConfig, keepIdle, keepInterval) + listenConfig.KeepAliveConfig = net.KeepAliveConfig{ + Enable: true, + Idle: keepIdle, + Interval: keepInterval, + } } if l.listenOptions.TCPMultiPath { - if !go121Available { - return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") - } - setMultiPathTCP(&listenConfig) + listenConfig.SetMultipathTCP(true) } if l.tproxy { listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error { diff --git a/common/tls/ech_stub.go b/common/tls/ech_stub.go deleted file mode 100644 index 357466c0..00000000 --- a/common/tls/ech_stub.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build !go1.24 - -package tls - -import ( - "context" - "crypto/tls" - - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) { - return nil, E.New("ECH requires go1.24, please recompile your binary.") -} - -func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, tlsConfig *tls.Config, echKeyPath *string) error { - return E.New("ECH requires go1.24, please recompile your binary.") -} - -func (c *STDServerConfig) setECHServerConfig(echKey []byte) error { - panic("unreachable") -} From ab0869c97293cab701ef2dff290568864ce7064e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Sep 2025 18:45:20 +0800 Subject: [PATCH 032/185] Update tfo-go to latest --- .github/workflows/build.yml | 2 +- .github/workflows/linux.yml | 2 +- Dockerfile | 2 +- Makefile | 2 +- cmd/internal/build_libbox/main.go | 2 +- common/dialer/default.go | 2 +- common/dialer/tfo.go | 2 +- common/listener/listener_tcp.go | 2 +- go.mod | 3 ++- go.sum | 6 ++++-- 10 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9819bc62..59972255 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -146,7 +146,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build if: matrix.os != 'android' diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 841edc10..6f84a899 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -85,7 +85,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build run: | diff --git a/Dockerfile b/Dockerfile index 62dba598..5273b2e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN set -ex \ && export COMMIT=$(git rev-parse --short HEAD) \ && export VERSION=$(go run ./cmd/internal/read_tag) \ && go build -v -trimpath -tags \ - "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname" \ + "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0" \ -o /go/bin/sing-box \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box diff --git a/Makefile b/Makefile index 39e34021..baeac709 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname +TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0 GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 974769d9..3400473c 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -62,7 +62,7 @@ func init() { sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0") - sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack", "badlinkname") + sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0") macOSTags = append(macOSTags, "with_dhcp") memcTags = append(memcTags, "with_tailscale") notMemcTags = append(notMemcTags, "with_low_memory") diff --git a/common/dialer/default.go b/common/dialer/default.go index f3481439..06f637a3 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -21,7 +21,7 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" - "github.com/metacubex/tfo-go" + "github.com/database64128/tfo-go/v2" ) var ( diff --git a/common/dialer/tfo.go b/common/dialer/tfo.go index ae04e71f..e8e93083 100644 --- a/common/dialer/tfo.go +++ b/common/dialer/tfo.go @@ -14,7 +14,7 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/metacubex/tfo-go" + "github.com/database64128/tfo-go/v2" ) type slowOpenConn struct { diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index 7ac7bbba..e63d643c 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -17,7 +17,7 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" - "github.com/metacubex/tfo-go" + "github.com/database64128/tfo-go/v2" ) func (l *Listener) ListenTCP() (net.Listener, error) { diff --git a/go.mod b/go.mod index c7a16b73..65ff01dd 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/caddyserver/certmagic v0.23.0 github.com/coder/websocket v1.8.13 github.com/cretz/bine v0.2.0 + github.com/database64128/tfo-go/v2 v2.3.1 github.com/go-chi/chi/v5 v5.2.2 github.com/go-chi/render v1.0.3 github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 @@ -15,7 +16,6 @@ require ( github.com/libdns/alidns v1.0.5-libdns.v1.beta1 github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 github.com/metacubex/utls v1.8.4 github.com/mholt/acmez/v3 v3.1.2 github.com/miekg/dns v1.1.67 @@ -66,6 +66,7 @@ require ( github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect + github.com/database64128/netx-go v0.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect diff --git a/go.sum b/go.sum index 635a7619..56d290a9 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,10 @@ github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= +github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM= +github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc= +github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw= +github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -111,8 +115,6 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE= -github.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= From dfd95b2615d24fc9b960cd9f87f5c1fe6b3b08b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Sep 2025 19:03:49 +0800 Subject: [PATCH 033/185] Fix WireGuard input packet --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 65ff01dd..5b1bff87 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 - github.com/sagernet/wireguard-go v0.0.2-beta.1 + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 56d290a9..c420641a 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1h github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos= github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw= -github.com/sagernet/wireguard-go v0.0.2-beta.1 h1:Zy7+dXpdViaRhP1pjtcMRfcbb7mWSxduCKzyEYbSJvk= -github.com/sagernet/wireguard-go v0.0.2-beta.1/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= From 79bbce3db3e249a44bbda3b1cdd8678566c22ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Sep 2025 08:59:46 +0800 Subject: [PATCH 034/185] Add curve preferences, pinned public key SHA256 and mTLS for TLS options --- common/tls/reality_server.go | 5 +- common/tls/std_client.go | 34 ++++ common/tls/std_server.go | 113 +++++++++--- common/tls/utls_client.go | 9 + docs/configuration/shared/tls.md | 111 +++++++++++- docs/configuration/shared/tls.zh.md | 261 ++++++++++++++++++++-------- option/tls.go | 180 +++++++++++++++---- 7 files changed, 576 insertions(+), 137 deletions(-) diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index b0b3e6e3..5fc68475 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -68,7 +68,10 @@ func NewRealityServer(ctx context.Context, logger log.ContextLogger, options opt return nil, E.New("unknown cipher_suite: ", cipherSuite) } } - if len(options.Certificate) > 0 || options.CertificatePath != "" { + if len(options.CurvePreferences) > 0 { + return nil, E.New("curve preferences is unavailable in reality") + } + if len(options.Certificate) > 0 || options.CertificatePath != "" || len(options.ClientCertificatePublicKeySHA256) > 0 { return nil, E.New("certificate is unavailable in reality") } if len(options.Key) > 0 || options.KeyPath != "" { diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 8aebc3f6..265444ab 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -1,9 +1,12 @@ package tls import ( + "bytes" "context" + "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/base64" "net" "os" "strings" @@ -108,6 +111,15 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres return err } } + if len(options.CertificatePublicKeySHA256) > 0 { + if len(options.Certificate) > 0 || options.CertificatePath != "" { + return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path") + } + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time) + } + } if len(options.ALPN) > 0 { tlsConfig.NextProtos = options.ALPN } @@ -137,6 +149,9 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres return nil, E.New("unknown cipher_suite: ", cipherSuite) } } + for _, curve := range options.CurvePreferences { + tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve)) + } var certificate []byte if len(options.Certificate) > 0 { certificate = []byte(strings.Join(options.Certificate, "\n")) @@ -175,3 +190,22 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres } return config, nil } + +func verifyPublicKeySHA256(knownHashValues [][]byte, rawCerts [][]byte, timeFunc func() time.Time) error { + leafCertificate, err := x509.ParseCertificate(rawCerts[0]) + if err != nil { + return E.Cause(err, "failed to parse leaf certificate") + } + + pubKeyBytes, err := x509.MarshalPKIXPublicKey(leafCertificate.PublicKey) + if err != nil { + return E.Cause(err, "failed to marshal public key") + } + hashValue := sha256.Sum256(pubKeyBytes) + for _, value := range knownHashValues { + if bytes.Equal(value, hashValue[:]) { + return nil + } + } + return E.New("unrecognized remote public key: ", base64.StdEncoding.EncodeToString(hashValue[:])) +} diff --git a/common/tls/std_server.go b/common/tls/std_server.go index d162ceb7..760c4b3a 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -3,6 +3,7 @@ package tls import ( "context" "crypto/tls" + "crypto/x509" "net" "os" "strings" @@ -22,16 +23,17 @@ import ( var errInsecureUnused = E.New("tls: insecure unused") type STDServerConfig struct { - access sync.RWMutex - config *tls.Config - logger log.Logger - acmeService adapter.SimpleLifecycle - certificate []byte - key []byte - certificatePath string - keyPath string - echKeyPath string - watcher *fswatch.Watcher + access sync.RWMutex + config *tls.Config + logger log.Logger + acmeService adapter.SimpleLifecycle + certificate []byte + key []byte + certificatePath string + keyPath string + clientCertificatePath []string + echKeyPath string + watcher *fswatch.Watcher } func (c *STDServerConfig) ServerName() string { @@ -111,6 +113,9 @@ func (c *STDServerConfig) startWatcher() error { if c.echKeyPath != "" { watchPath = append(watchPath, c.echKeyPath) } + if len(c.clientCertificatePath) > 0 { + watchPath = append(watchPath, c.clientCertificatePath...) + } if len(watchPath) == 0 { return nil } @@ -159,6 +164,30 @@ func (c *STDServerConfig) certificateUpdated(path string) error { c.config = config c.access.Unlock() c.logger.Info("reloaded TLS certificate") + } else if common.Contains(c.clientCertificatePath, path) { + clientCertificateCA := x509.NewCertPool() + var reloaded bool + for _, certPath := range c.clientCertificatePath { + content, err := os.ReadFile(certPath) + if err != nil { + c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath)) + continue + } + if !clientCertificateCA.AppendCertsFromPEM(content) { + c.logger.Error(E.New("invalid client certificate file: ", certPath)) + continue + } + reloaded = true + } + if !reloaded { + return E.New("client certificates is empty") + } + c.access.Lock() + config := c.config.Clone() + config.ClientCAs = clientCertificateCA + c.config = config + c.access.Unlock() + c.logger.Info("reloaded client certificates") } else if path == c.echKeyPath { echKey, err := os.ReadFile(c.echKeyPath) if err != nil { @@ -235,8 +264,14 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option. return nil, E.New("unknown cipher_suite: ", cipherSuite) } } - var certificate []byte - var key []byte + for _, curveID := range options.CurvePreferences { + tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curveID)) + } + tlsConfig.ClientAuth = tls.ClientAuthType(options.ClientAuthentication) + var ( + certificate []byte + key []byte + ) if acmeService == nil { if len(options.Certificate) > 0 { certificate = []byte(strings.Join(options.Certificate, "\n")) @@ -278,6 +313,43 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option. tlsConfig.Certificates = []tls.Certificate{keyPair} } } + if len(options.ClientCertificate) > 0 || len(options.ClientCertificatePath) > 0 { + if tlsConfig.ClientAuth == tls.NoClientCert { + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + } + if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert { + if len(options.ClientCertificate) > 0 { + clientCertificateCA := x509.NewCertPool() + if !clientCertificateCA.AppendCertsFromPEM([]byte(strings.Join(options.ClientCertificate, "\n"))) { + return nil, E.New("invalid client certificate strings") + } + tlsConfig.ClientCAs = clientCertificateCA + } else if len(options.ClientCertificatePath) > 0 { + clientCertificateCA := x509.NewCertPool() + for _, path := range options.ClientCertificatePath { + content, err := os.ReadFile(path) + if err != nil { + return nil, E.Cause(err, "read client certificate from ", path) + } + if !clientCertificateCA.AppendCertsFromPEM(content) { + return nil, E.New("invalid client certificate file: ", path) + } + } + tlsConfig.ClientCAs = clientCertificateCA + } else if len(options.ClientCertificatePublicKeySHA256) > 0 { + if tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert { + tlsConfig.ClientAuth = tls.RequireAnyClientCert + } else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven { + tlsConfig.ClientAuth = tls.RequestClientCert + } + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return verifyPublicKeySHA256(options.ClientCertificatePublicKeySHA256, rawCerts, tlsConfig.Time) + } + } else { + return nil, E.New("missing client_certificate, client_certificate_path or client_certificate_public_key_sha256 for client authentication") + } + } var echKeyPath string if options.ECH != nil && options.ECH.Enabled { err = parseECHServerConfig(ctx, options, tlsConfig, &echKeyPath) @@ -286,14 +358,15 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option. } } serverConfig := &STDServerConfig{ - config: tlsConfig, - logger: logger, - acmeService: acmeService, - certificate: certificate, - key: key, - certificatePath: options.CertificatePath, - keyPath: options.KeyPath, - echKeyPath: echKeyPath, + config: tlsConfig, + logger: logger, + acmeService: acmeService, + certificate: certificate, + key: key, + certificatePath: options.CertificatePath, + clientCertificatePath: options.ClientCertificatePath, + keyPath: options.KeyPath, + echKeyPath: echKeyPath, } serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) { serverConfig.access.Lock() diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index 9f8138d7..a277b992 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -167,6 +167,15 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre } tlsConfig.InsecureServerNameToVerify = serverName } + if len(options.CertificatePublicKeySHA256) > 0 { + if len(options.Certificate) > 0 || options.CertificatePath != "" { + return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path") + } + tlsConfig.InsecureSkipVerify = true + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time) + } + } if len(options.ALPN) > 0 { tlsConfig.NextProtos = options.ALPN } diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index fa53ba12..74ddea05 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -5,7 +5,13 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" :material-plus: [kernel_tx](#kernel_tx) - :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [curve_preferences](#curve_preferences) + :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) + :material-plus: [client_authentication](#client_authentication) + :material-plus: [client_certificate](#client_certificate) + :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) !!! quote "Changes in sing-box 1.12.0" @@ -29,8 +35,13 @@ icon: material/new-box "min_version": "", "max_version": "", "cipher_suites": [], + "curve_preferences": [], "certificate": [], "certificate_path": "", + "client_authentication": "", + "client_certificate": [], + "client_certificate_path": [], + "client_certificate_public_key_sha256": [], "key": [], "key_path": "", "kernel_tx": false, @@ -92,6 +103,7 @@ icon: material/new-box "cipher_suites": [], "certificate": "", "certificate_path": "", + "certificate_public_key_sha256": [], "fragment": false, "fragment_fallback_delay": "", "record_fragment": false, @@ -195,14 +207,29 @@ By default, the maximum version is currently TLS 1.3. #### cipher_suites -A list of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored. +List of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored. Note that TLS 1.3 cipher suites are not configurable. If empty, a safe default list is used. The default cipher suites might change over time. +#### curve_preferences + +!!! question "Since sing-box 1.13.0" + +Set of supported key exchange mechanisms. The order of the list is ignored, and key exchange mechanisms are chosen +from this list using an internal preference order by Golang. + +Available values, also the default list: + +* `P256` +* `P384` +* `P521` +* `X25519` +* `X25519MLKEM768` + #### certificate -The server certificate line array, in PEM format. +Server certificates chain line array, in PEM format. #### certificate_path @@ -210,7 +237,26 @@ The server certificate line array, in PEM format. Will be automatically reloaded if file modified. -The path to the server certificate, in PEM format. +The path to server certificate chain, in PEM format. + + +#### certificate_public_key_sha256 + +!!! question "Since sing-box 1.13.0" + +==Client only== + +List of SHA-256 hashes of server certificate public keys, in base64 format. + +To generate the SHA-256 hash for a certificate's public key, use the following commands: + +```bash +# For a certificate file +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# For a certificate from a remote server +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` #### key @@ -228,6 +274,63 @@ The server private key line array, in PEM format. The path to the server private key, in PEM format. +#### client_authentication + +!!! question "Since sing-box 1.13.0" + +==Server only== + +The type of client authentication to use. + +Available values: + +* `no` (default) +* `request` +* `require-any` +* `verify-if-given` +* `require-and-verify` + +One of `client_certificate`, `client_certificate_path`, or `client_certificate_public_key_sha256` is required +if this option is set to `verify-if-given`, or `require-and-verify`. + +#### client_certificate + +!!! question "Since sing-box 1.13.0" + +==Server only== + +Client certificate chain line array, in PEM format. + +#### client_certificate_path + +!!! question "Since sing-box 1.13.0" + +==Server only== + +!!! note "" + + Will be automatically reloaded if file modified. + +List of path to client certificate chain, in PEM format. + +#### client_certificate_public_key_sha256 + +!!! question "Since sing-box 1.13.0" + +==Server only== + +List of SHA-256 hashes of client certificate public keys, in base64 format. + +To generate the SHA-256 hash for a certificate's public key, use the following commands: + +```bash +# For a certificate file +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# For a certificate from a remote server +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` + #### kernel_tx !!! question "Since sing-box 1.13.0" diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 32f85bee..8a461fb2 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -1,18 +1,24 @@ --- -icon: material/alert-decagram +icon: material/new-box --- !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_tx](#kernel_tx) :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [curve_preferences](#curve_preferences) + :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) + :material-plus: [client_authentication](#client_authentication) + :material-plus: [client_certificate](#client_certificate) + :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) !!! quote "sing-box 1.12.0 中的更改" - :material-plus: [tls_fragment](#tls_fragment) - :material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay) - :material-plus: [tls_record_fragment](#tls_record_fragment) - :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled) + :material-plus: [fragment](#fragment) + :material-plus: [fragment_fallback_delay](#fragment_fallback_delay) + :material-plus: [record_fragment](#record_fragment) + :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled) :material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled) !!! quote "sing-box 1.10.0 中的更改" @@ -29,8 +35,13 @@ icon: material/alert-decagram "min_version": "", "max_version": "", "cipher_suites": [], + "curve_preferences": [], "certificate": [], "certificate_path": "", + "client_authentication": "", + "client_certificate": [], + "client_certificate_path": [], + "client_certificate_public_key_sha256": [], "key": [], "key_path": "", "kernel_tx": false, @@ -90,17 +101,20 @@ icon: material/alert-decagram "min_version": "", "max_version": "", "cipher_suites": [], - "certificate": [], + "certificate": "", "certificate_path": "", + "certificate_public_key_sha256": [], "fragment": false, "fragment_fallback_delay": "", "record_fragment": false, "ech": { "enabled": false, - "pq_signature_schemes_enabled": false, - "dynamic_record_sizing_disabled": false, "config": [], - "config_path": "" + "config_path": "", + + // 废弃的 + "pq_signature_schemes_enabled": false, + "dynamic_record_sizing_disabled": false }, "utls": { "enabled": false, @@ -191,13 +205,27 @@ TLS 版本值: #### cipher_suites -启用的 TLS 1.0-1.2密码套件的列表。列表的顺序被忽略。请注意,TLS 1.3 的密码套件是不可配置的。 +启用的 TLS 1.0–1.2 密码套件列表。列表的顺序被忽略。请注意,TLS 1.3 的密码套件是不可配置的。 如果为空,则使用安全的默认列表。默认密码套件可能会随着时间的推移而改变。 +#### curve_preferences + +!!! question "自 sing-box 1.13.0 起" + +支持的密钥交换机制集合。列表的顺序被忽略,密钥交换机制通过 Golang 的内部偏好顺序从此列表中选择。 + +可用值,同时也是默认列表: + +* `P256` +* `P384` +* `P521` +* `X25519` +* `X25519MLKEM768` + #### certificate -服务器 PEM 证书行数组。 +服务器证书链行数组,PEM 格式。 #### certificate_path @@ -205,7 +233,25 @@ TLS 版本值: 文件更改时将自动重新加载。 -服务器 PEM 证书路径。 +服务器证书链路径,PEM 格式。 + +#### certificate_public_key_sha256 + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +服务器证书公钥的 SHA-256 哈希列表,base64 格式。 + +要生成证书公钥的 SHA-256 哈希,请使用以下命令: + +```bash +# 对于证书文件 +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# 对于远程服务器的证书 +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` #### key @@ -221,7 +267,68 @@ TLS 版本值: ==仅服务器== -服务器 PEM 私钥路径。 +!!! note "" + + 文件更改时将自动重新加载。 + +服务器私钥路径,PEM 格式。 + +#### client_authentication + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +要使用的客户端身份验证类型。 + +可用值: + +* `no`(默认) +* `request` +* `require-any` +* `verify-if-given` +* `require-and-verify` + +如果此选项设置为 `verify-if-given` 或 `require-and-verify`, +则需要 `client_certificate`、`client_certificate_path` 或 `client_certificate_public_key_sha256` 中的一个。 + +#### client_certificate + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +客户端证书链行数组,PEM 格式。 + +#### client_certificate_path + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +!!! note "" + + 文件更改时将自动重新加载。 + +客户端证书链路径列表,PEM 格式。 + +#### client_certificate_public_key_sha256 + +!!! question "自 sing-box 1.13.0 起" + +==仅服务器== + +客户端证书公钥的 SHA-256 哈希列表,base64 格式。 + +要生成证书公钥的 SHA-256 哈希,请使用以下命令: + +```bash +# 对于证书文件 +openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 + +# 对于远程服务器的证书 +echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 +``` #### kernel_tx @@ -307,44 +414,11 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻 默认使用 chrome 指纹。 -## ECH 字段 +### ECH 字段 -ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分 -信息。 +ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分信息。 -ECH 配置和密钥可以通过 `sing-box generate ech-keypair [--pq-signature-schemes-enabled]` 生成。 - -#### key - -==仅服务器== - -ECH PEM 密钥行数组 - -#### key_path - -==仅服务器== - -!!! note "" - - 文件更改时将自动重新加载。 - -ECH PEM 密钥路径 - -#### config - -==仅客户端== - -ECH PEM 配置行数组 - -如果为空,将尝试从 DNS 加载。 - -#### config_path - -==仅客户端== - -ECH PEM 配置路径 - -如果为空,将尝试从 DNS 加载。 +ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。 #### pq_signature_schemes_enabled @@ -354,8 +428,6 @@ ECH PEM 配置路径 启用对后量子对等证书签名方案的支持。 -建议匹配 `sing-box generate ech-keypair` 的参数。 - #### dynamic_record_sizing_disabled !!! failure "已在 sing-box 1.12.0 废弃" @@ -364,57 +436,91 @@ ECH PEM 配置路径 禁用 TLS 记录的自适应大小调整。 -如果为 true,则始终使用最大可能的 TLS 记录大小。 -如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。 +当为 true 时,总是使用最大可能的 TLS 记录大小。 +当为 false 时,可能会调整 TLS 记录的大小以尝试改善延迟。 -#### tls_fragment +#### key + +==仅服务器== + +ECH 密钥行数组,PEM 格式。 + +#### key_path + +==仅服务器== + +!!! note "" + + 文件更改时将自动重新加载。 + +ECH 密钥路径,PEM 格式。 + +#### config + +==仅客户端== + +ECH 配置行数组,PEM 格式。 + +如果为空,将尝试从 DNS 加载。 + +#### config_path + +==仅客户端== + +ECH 配置路径,PEM 格式。 + +如果为空,将尝试从 DNS 加载。 + +#### fragment !!! question "自 sing-box 1.12.0 起" ==仅客户端== -通过分段 TLS 握手数据包来绕过防火墙检测。 +通过分段 TLS 握手数据包来绕过防火墙。 -此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。 +此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真正的审查。 -由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。 +由于性能不佳,请首先尝试 `record_fragment`,且仅应用于已知被阻止的服务器名称。 -在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。 -若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。 +在 Linux、Apple 平台和(需要管理员权限的)Windows 系统上, +可以自动检测等待时间。否则,将回退到 +等待 `fragment_fallback_delay` 指定的固定时间。 -此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。 +此外,如果实际等待时间少于 20ms,也会回退到等待固定时间, +因为目标被认为是本地的或在透明代理后面。 -#### tls_fragment_fallback_delay +#### fragment_fallback_delay !!! question "自 sing-box 1.12.0 起" ==仅客户端== -当 TLS 分片功能无法自动判定等待时间时使用的回退值。 +当 TLS 分段无法自动确定等待时间时使用的回退值。 默认使用 `500ms`。 -#### tls_record_fragment - -==仅客户端== +#### record_fragment !!! question "自 sing-box 1.12.0 起" -通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。 +==仅客户端== + +将 TLS 握手分段为多个 TLS 记录以绕过防火墙。 ### ACME 字段 #### domain -一组域名。 +域名列表。 -默认禁用 ACME。 +如果为空则禁用 ACME。 #### data_directory -ACME 数据目录。 +ACME 数据存储目录。 -默认使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。 +如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。 #### default_server_name @@ -452,12 +558,11 @@ ACME 数据目录。 #### external_account -EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知帐户所需的信息由 CA。 +EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。 -外部帐户绑定“用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。 +外部帐户绑定"用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。 -为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要向 ACME 客户端提供 MAC 密钥和密钥标识符,使用 ACME 之外的一些机制。 -§7.3.4 +为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4 #### external_account.key_id @@ -507,6 +612,8 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。 #### max_time_difference -服务器与和客户端之间允许的最大时间差。 +==仅服务器== -默认禁用检查。 +服务器和客户端之间的最大时间差。 + +如果为空则禁用检查。 diff --git a/option/tls.go b/option/tls.go index db51ed1a..90f9a55a 100644 --- a/option/tls.go +++ b/option/tls.go @@ -1,24 +1,80 @@ package option -import "github.com/sagernet/sing/common/json/badoption" +import ( + "crypto/tls" + "encoding/json" + "strings" + + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json/badoption" +) type InboundTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - ServerName string `json:"server_name,omitempty"` - Insecure bool `json:"insecure,omitempty"` - ALPN badoption.Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` - Certificate badoption.Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - Key badoption.Listable[string] `json:"key,omitempty"` - KeyPath string `json:"key_path,omitempty"` - KernelTx bool `json:"kernel_tx,omitempty"` - KernelRx bool `json:"kernel_rx,omitempty"` - ACME *InboundACMEOptions `json:"acme,omitempty"` - ECH *InboundECHOptions `json:"ech,omitempty"` - Reality *InboundRealityOptions `json:"reality,omitempty"` + Enabled bool `json:"enabled,omitempty"` + ServerName string `json:"server_name,omitempty"` + Insecure bool `json:"insecure,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty"` + MinVersion string `json:"min_version,omitempty"` + MaxVersion string `json:"max_version,omitempty"` + CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` + CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath string `json:"certificate_path,omitempty"` + ClientAuthentication ClientAuthType `json:"client_authentication,omitempty"` + ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"` + ClientCertificatePath badoption.Listable[string] `json:"client_certificate_path,omitempty"` + ClientCertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"client_certificate_public_key_sha256,omitempty"` + Key badoption.Listable[string] `json:"key,omitempty"` + KeyPath string `json:"key_path,omitempty"` + KernelTx bool `json:"kernel_tx,omitempty"` + KernelRx bool `json:"kernel_rx,omitempty"` + ACME *InboundACMEOptions `json:"acme,omitempty"` + ECH *InboundECHOptions `json:"ech,omitempty"` + Reality *InboundRealityOptions `json:"reality,omitempty"` +} + +type ClientAuthType tls.ClientAuthType + +func (t ClientAuthType) MarshalJSON() ([]byte, error) { + var stringValue string + switch t { + case ClientAuthType(tls.NoClientCert): + stringValue = "no" + case ClientAuthType(tls.RequestClientCert): + stringValue = "request" + case ClientAuthType(tls.RequireAnyClientCert): + stringValue = "require-any" + case ClientAuthType(tls.VerifyClientCertIfGiven): + stringValue = "verify-if-given" + case ClientAuthType(tls.RequireAndVerifyClientCert): + stringValue = "require-and-verify" + default: + return nil, E.New("unknown client authentication type: ", int(t)) + } + return json.Marshal(stringValue) +} + +func (t *ClientAuthType) UnmarshalJSON(data []byte) error { + var stringValue string + err := json.Unmarshal(data, &stringValue) + if err != nil { + return err + } + switch stringValue { + case "no": + *t = ClientAuthType(tls.NoClientCert) + case "request": + *t = ClientAuthType(tls.RequestClientCert) + case "require-any": + *t = ClientAuthType(tls.RequireAnyClientCert) + case "verify-if-given": + *t = ClientAuthType(tls.VerifyClientCertIfGiven) + case "require-and-verify": + *t = ClientAuthType(tls.RequireAndVerifyClientCert) + default: + return E.New("unknown client authentication type: ", stringValue) + } + return nil } type InboundTLSOptionsContainer struct { @@ -39,24 +95,26 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL } type OutboundTLSOptions struct { - Enabled bool `json:"enabled,omitempty"` - DisableSNI bool `json:"disable_sni,omitempty"` - ServerName string `json:"server_name,omitempty"` - Insecure bool `json:"insecure,omitempty"` - ALPN badoption.Listable[string] `json:"alpn,omitempty"` - MinVersion string `json:"min_version,omitempty"` - MaxVersion string `json:"max_version,omitempty"` - CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` - Certificate badoption.Listable[string] `json:"certificate,omitempty"` - CertificatePath string `json:"certificate_path,omitempty"` - Fragment bool `json:"fragment,omitempty"` - FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"` - RecordFragment bool `json:"record_fragment,omitempty"` - KernelTx bool `json:"kernel_tx,omitempty"` - KernelRx bool `json:"kernel_rx,omitempty"` - ECH *OutboundECHOptions `json:"ech,omitempty"` - UTLS *OutboundUTLSOptions `json:"utls,omitempty"` - Reality *OutboundRealityOptions `json:"reality,omitempty"` + Enabled bool `json:"enabled,omitempty"` + DisableSNI bool `json:"disable_sni,omitempty"` + ServerName string `json:"server_name,omitempty"` + Insecure bool `json:"insecure,omitempty"` + ALPN badoption.Listable[string] `json:"alpn,omitempty"` + MinVersion string `json:"min_version,omitempty"` + MaxVersion string `json:"max_version,omitempty"` + CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"` + CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"` + Certificate badoption.Listable[string] `json:"certificate,omitempty"` + CertificatePath string `json:"certificate_path,omitempty"` + CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"` + Fragment bool `json:"fragment,omitempty"` + FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"` + RecordFragment bool `json:"record_fragment,omitempty"` + KernelTx bool `json:"kernel_tx,omitempty"` + KernelRx bool `json:"kernel_rx,omitempty"` + ECH *OutboundECHOptions `json:"ech,omitempty"` + UTLS *OutboundUTLSOptions `json:"utls,omitempty"` + Reality *OutboundRealityOptions `json:"reality,omitempty"` } type OutboundTLSOptionsContainer struct { @@ -76,6 +134,58 @@ func (o *OutboundTLSOptionsContainer) ReplaceOutboundTLSOptions(options *Outboun o.TLS = options } +type CurvePreference tls.CurveID + +const ( + CurveP256 = 23 + CurveP384 = 24 + CurveP521 = 25 + X25519 = 29 + X25519MLKEM768 = 4588 +) + +func (c CurvePreference) MarshalJSON() ([]byte, error) { + var stringValue string + switch c { + case CurvePreference(CurveP256): + stringValue = "P256" + case CurvePreference(CurveP384): + stringValue = "P384" + case CurvePreference(CurveP521): + stringValue = "P521" + case CurvePreference(X25519): + stringValue = "X25519" + case CurvePreference(X25519MLKEM768): + stringValue = "X25519MLKEM768" + default: + return nil, E.New("unknown curve id: ", int(c)) + } + return json.Marshal(stringValue) +} + +func (c *CurvePreference) UnmarshalJSON(data []byte) error { + var stringValue string + err := json.Unmarshal(data, &stringValue) + if err != nil { + return err + } + switch strings.ToUpper(stringValue) { + case "P256": + *c = CurvePreference(CurveP256) + case "P384": + *c = CurvePreference(CurveP384) + case "P521": + *c = CurvePreference(CurveP521) + case "X25519": + *c = CurvePreference(X25519) + case "X25519MLKEM768": + *c = CurvePreference(X25519MLKEM768) + default: + return E.New("unknown curve name: ", stringValue) + } + return nil +} + type InboundRealityOptions struct { Enabled bool `json:"enabled,omitempty"` Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"` From 3dc285be8c0cb2606acfb286491988296319f86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 9 Oct 2025 23:10:34 +0800 Subject: [PATCH 035/185] Fix missing mTLS support in client options --- common/tls/std_client.go | 29 ++++++++++++++++ common/tls/utls_client.go | 29 ++++++++++++++++ docs/configuration/shared/tls.md | 53 +++++++++++++++++++++++++---- docs/configuration/shared/tls.zh.md | 41 +++++++++++++++++++++- option/tls.go | 4 +++ 5 files changed, 148 insertions(+), 8 deletions(-) diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 265444ab..1611c83e 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -169,6 +169,35 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres } tlsConfig.RootCAs = certPool } + var clientCertificate []byte + if len(options.ClientCertificate) > 0 { + clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n")) + } else if options.ClientCertificatePath != "" { + content, err := os.ReadFile(options.ClientCertificatePath) + if err != nil { + return nil, E.Cause(err, "read client certificate") + } + clientCertificate = content + } + var clientKey []byte + if len(options.ClientKey) > 0 { + clientKey = []byte(strings.Join(options.ClientKey, "\n")) + } else if options.ClientKeyPath != "" { + content, err := os.ReadFile(options.ClientKeyPath) + if err != nil { + return nil, E.Cause(err, "read client key") + } + clientKey = content + } + if len(clientCertificate) > 0 && len(clientKey) > 0 { + keyPair, err := tls.X509KeyPair(clientCertificate, clientKey) + if err != nil { + return nil, E.Cause(err, "parse client x509 key pair") + } + tlsConfig.Certificates = []tls.Certificate{keyPair} + } else if len(clientCertificate) > 0 || len(clientKey) > 0 { + return nil, E.New("client certificate and client key must be provided together") + } var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment} if options.ECH != nil && options.ECH.Enabled { var err error diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index a277b992..941192ba 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -222,6 +222,35 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre } tlsConfig.RootCAs = certPool } + var clientCertificate []byte + if len(options.ClientCertificate) > 0 { + clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n")) + } else if options.ClientCertificatePath != "" { + content, err := os.ReadFile(options.ClientCertificatePath) + if err != nil { + return nil, E.Cause(err, "read client certificate") + } + clientCertificate = content + } + var clientKey []byte + if len(options.ClientKey) > 0 { + clientKey = []byte(strings.Join(options.ClientKey, "\n")) + } else if options.ClientKeyPath != "" { + content, err := os.ReadFile(options.ClientKeyPath) + if err != nil { + return nil, E.Cause(err, "read client key") + } + clientKey = content + } + if len(clientCertificate) > 0 && len(clientKey) > 0 { + keyPair, err := utls.X509KeyPair(clientCertificate, clientKey) + if err != nil { + return nil, E.Cause(err, "parse client x509 key pair") + } + tlsConfig.Certificates = []utls.Certificate{keyPair} + } else if len(clientCertificate) > 0 || len(clientKey) > 0 { + return nil, E.New("client certificate and client key must be provided together") + } id, err := uTLSClientHelloID(options.UTLS.Fingerprint) if err != nil { return nil, err diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 74ddea05..b1ec1e66 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -4,13 +4,15 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [kernel_tx](#kernel_tx) - :material-plus: [kernel_rx](#kernel_rx) - :material-plus: [curve_preferences](#curve_preferences) - :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) - :material-plus: [client_authentication](#client_authentication) - :material-plus: [client_certificate](#client_certificate) - :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [curve_preferences](#curve_preferences) + :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) + :material-plus: [client_certificate](#client_certificate) + :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_key](#client_key) + :material-plus: [client_key_path](#client_key_path) + :material-plus: [client_authentication](#client_authentication) :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) !!! quote "Changes in sing-box 1.12.0" @@ -101,9 +103,14 @@ icon: material/new-box "min_version": "", "max_version": "", "cipher_suites": [], + "curve_preferences": [], "certificate": "", "certificate_path": "", "certificate_public_key_sha256": [], + "client_certificate": [], + "client_certificate_path": "", + "client_key": [], + "client_key_path": "", "fragment": false, "fragment_fallback_delay": "", "record_fragment": false, @@ -258,6 +265,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 ``` +#### client_certificate + +!!! question "Since sing-box 1.13.0" + +==Client only== + +Client certificate chain line array, in PEM format. + +#### client_certificate_path + +!!! question "Since sing-box 1.13.0" + +==Client only== + +The path to client certificate chain, in PEM format. + +#### client_key + +!!! question "Since sing-box 1.13.0" + +==Client only== + +Client private key line array, in PEM format. + +#### client_key_path + +!!! question "Since sing-box 1.13.0" + +==Client only== + +The path to client private key, in PEM format. + #### key ==Server only== diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 8a461fb2..1cc07b32 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -8,9 +8,11 @@ icon: material/new-box :material-plus: [kernel_rx](#kernel_rx) :material-plus: [curve_preferences](#curve_preferences) :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) - :material-plus: [client_authentication](#client_authentication) :material-plus: [client_certificate](#client_certificate) :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_key](#client_key) + :material-plus: [client_key_path](#client_key_path) + :material-plus: [client_authentication](#client_authentication) :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) !!! quote "sing-box 1.12.0 中的更改" @@ -101,9 +103,14 @@ icon: material/new-box "min_version": "", "max_version": "", "cipher_suites": [], + "curve_preferences": [], "certificate": "", "certificate_path": "", "certificate_public_key_sha256": [], + "client_certificate": [], + "client_certificate_path": "", + "client_key": [], + "client_key_path": "", "fragment": false, "fragment_fallback_delay": "", "record_fragment": false, @@ -253,6 +260,38 @@ openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform d echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 ``` +#### client_certificate + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端证书链行数组,PEM 格式。 + +#### client_certificate_path + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端证书链路径,PEM 格式。 + +#### client_key + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端私钥行数组,PEM 格式。 + +#### client_key_path + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +客户端私钥路径,PEM 格式。 + #### key ==仅服务器== diff --git a/option/tls.go b/option/tls.go index 90f9a55a..1829898a 100644 --- a/option/tls.go +++ b/option/tls.go @@ -107,6 +107,10 @@ type OutboundTLSOptions struct { Certificate badoption.Listable[string] `json:"certificate,omitempty"` CertificatePath string `json:"certificate_path,omitempty"` CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"` + ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"` + ClientCertificatePath string `json:"client_certificate_path,omitempty"` + ClientKey badoption.Listable[string] `json:"client_key,omitempty"` + ClientKeyPath string `json:"client_key_path,omitempty"` Fragment bool `json:"fragment,omitempty"` FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"` RecordFragment bool `json:"record_fragment,omitempty"` From fce21607bde8fb7db131016609305f93fc873d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 16 Oct 2025 23:25:34 +0800 Subject: [PATCH 036/185] Use a more conservative strategy for resolving with systemd-resolved for local DNS server --- dns/transport/local/local.go | 14 ++++---- dns/transport/local/local_resolved_linux.go | 36 +++++++++++++++------ dns/transport/local/local_resolved_stub.go | 4 +++ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index f1d67d16..51b8c18c 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -53,13 +53,15 @@ func (t *Transport) Start(stage adapter.StartStage) error { switch stage { case adapter.StartStateInitialize: if !t.preferGo { - resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) - if err == nil { - err = resolvedResolver.Start() + if isSystemdResolvedManaged() { + resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) if err == nil { - t.resolved = resolvedResolver - } else { - t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + err = resolvedResolver.Start() + if err == nil { + t.resolved = resolvedResolver + } else { + t.logger.Warn(E.Cause(err, "initialize resolved resolver")) + } } } } diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go index 3c9bf0c4..bac34c02 100644 --- a/dns/transport/local/local_resolved_linux.go +++ b/dns/transport/local/local_resolved_linux.go @@ -1,9 +1,11 @@ package local import ( + "bufio" "context" "errors" "os" + "strings" "sync" "sync/atomic" @@ -22,6 +24,25 @@ import ( mDNS "github.com/miekg/dns" ) +func isSystemdResolvedManaged() bool { + resolvContent, err := os.Open("/etc/resolv.conf") + if err != nil { + return false + } + defer resolvContent.Close() + scanner := bufio.NewScanner(resolvContent) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] != '#' { + return false + } + if strings.Contains(line, "systemd-resolved") { + return true + } + } + return false +} + type DBusResolvedResolver struct { ctx context.Context logger logger.ContextLogger @@ -188,7 +209,7 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObje int32(defaultInterface.Index), ) if call.Err != nil { - return nil, err + return nil, call.Err } var linkPath dbus.ObjectPath err = call.Store(&linkPath) @@ -214,15 +235,12 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObje return nil, E.New("No appropriate name servers or networks for name found") } } - return &ResolvedObject{ - BusObject: dbusObject, - }, nil - } else { - return &ResolvedObject{ - BusObject: dbusObject, - InterfaceIndex: int32(defaultInterface.Index), - }, nil + return nil, E.New("link has no DNS servers configured") } + return &ResolvedObject{ + BusObject: dbusObject, + InterfaceIndex: int32(defaultInterface.Index), + }, nil } func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) { diff --git a/dns/transport/local/local_resolved_stub.go b/dns/transport/local/local_resolved_stub.go index ac23c4f3..2e011851 100644 --- a/dns/transport/local/local_resolved_stub.go +++ b/dns/transport/local/local_resolved_stub.go @@ -9,6 +9,10 @@ import ( "github.com/sagernet/sing/common/logger" ) +func isSystemdResolvedManaged() bool { + return false +} + func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { return nil, os.ErrInvalid } From d87c9fd24263541cac000e029062ecb4a02e0950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 17 Oct 2025 16:30:13 +0800 Subject: [PATCH 037/185] Fix compatibility with MPTCP --- docs/configuration/inbound/tun.md | 19 +++++++++++++++++++ docs/configuration/inbound/tun.zh.md | 19 +++++++++++++++++++ option/tun.go | 1 + protocol/tun/inbound.go | 1 + 4 files changed, 40 insertions(+) diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 92313c82..5dde6b20 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [exclude_mptcp](#exclude_mptcp) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [loopback_address](#loopback_address) @@ -63,6 +67,7 @@ icon: material/new-box "auto_redirect": true, "auto_redirect_input_mark": "0x2023", "auto_redirect_output_mark": "0x2024", + "exclude_mptcp": false, "loopback_address": [ "10.7.0.1" ], @@ -278,6 +283,20 @@ Connection output mark used by `auto_redirect`. `0x2024` is used by default. +#### exclude_mptcp + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled. + +MPTCP cannot be transparently proxied due to protocol limitations. + +Such traffic is usually created by Apple systems. + +When enabled, MPTCP connections will bypass sing-box and connect directly, otherwise, will be rejected to avoid errors by default. + #### loopback_address !!! question "Since sing-box 1.12.0" diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index 9c053999..e9dec46f 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -2,6 +2,10 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [exclude_mptcp](#exclude_mptcp) + !!! quote "sing-box 1.12.0 中的更改" :material-plus: [loopback_address](#loopback_address) @@ -63,6 +67,7 @@ icon: material/new-box "auto_redirect": true, "auto_redirect_input_mark": "0x2023", "auto_redirect_output_mark": "0x2024", + "exclude_mptcp": false, "loopback_address": [ "10.7.0.1" ], @@ -277,6 +282,20 @@ tun 接口的 IPv6 前缀。 默认使用 `0x2024`。 +#### exclude_mptcp + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + +由于协议限制,MPTCP 无法被透明代理。 + +此类流量通常由 Apple 系统创建。 + +启用时,MPTCP 连接将绕过 sing-box 直接连接,否则,将被拒绝以避免错误。 + #### loopback_address !!! question "自 sing-box 1.12.0 起" diff --git a/option/tun.go b/option/tun.go index 89affb23..ca8e3a11 100644 --- a/option/tun.go +++ b/option/tun.go @@ -20,6 +20,7 @@ type TunInboundOptions struct { AutoRedirect bool `json:"auto_redirect,omitempty"` AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` + ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"` LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"` StrictRoute bool `json:"strict_route,omitempty"` RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"` diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 3f013598..20b1cd2f 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -203,6 +203,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo IPRoute2RuleIndex: ruleIndex, AutoRedirectInputMark: inputMark, AutoRedirectOutputMark: outputMark, + ExcludeMPTCP: options.ExcludeMPTCP, Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4), Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6), StrictRoute: options.StrictRoute, From 0f5cda4169998eb4a485b67707b77b9384406a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 21 Oct 2025 10:34:05 +0800 Subject: [PATCH 038/185] Add claude code multiplexer service --- .github/workflows/build.yml | 78 +++- .github/workflows/linux.yml | 2 +- .goreleaser.fury.yaml | 103 ----- .goreleaser.yaml | 213 ---------- Dockerfile | 2 +- Makefile | 2 +- constant/proxy.go | 1 + docs/configuration/service/ccm.md | 104 +++++ docs/configuration/service/ccm.zh.md | 104 +++++ docs/configuration/service/index.md | 1 + docs/configuration/service/index.zh.md | 1 + go.mod | 6 + go.sum | 14 + include/ccm.go | 12 + include/ccm_stub.go | 20 + include/ccm_stub_darwin.go | 20 + include/registry.go | 1 + option/ccm.go | 20 + release/local/common.sh | 100 +++++ release/local/debug.sh | 26 +- release/local/install.sh | 25 +- release/local/reinstall.sh | 23 +- release/local/uninstall.sh | 32 +- release/local/update.sh | 12 +- service/ccm/credential.go | 136 +++++++ service/ccm/credential_darwin.go | 116 ++++++ service/ccm/credential_other.go | 25 ++ service/ccm/service.go | 541 +++++++++++++++++++++++++ service/ccm/service_usage.go | 407 +++++++++++++++++++ service/ccm/service_user.go | 29 ++ 30 files changed, 1807 insertions(+), 369 deletions(-) delete mode 100644 .goreleaser.fury.yaml delete mode 100644 .goreleaser.yaml create mode 100644 docs/configuration/service/ccm.md create mode 100644 docs/configuration/service/ccm.zh.md create mode 100644 include/ccm.go create mode 100644 include/ccm_stub.go create mode 100644 include/ccm_stub_darwin.go create mode 100644 option/ccm.go create mode 100755 release/local/common.sh create mode 100644 service/ccm/credential.go create mode 100644 service/ccm/credential_darwin.go create mode 100644 service/ccm/credential_other.go create mode 100644 service/ccm/service.go create mode 100644 service/ccm/service_usage.go create mode 100644 service/ccm/service_user.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59972255..3d2aee6c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,10 +93,6 @@ jobs: - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" } - { os: windows, arch: arm64 } - - { os: darwin, arch: amd64 } - - { os: darwin, arch: arm64 } - - { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" } - - { os: android, arch: arm64, ndk: "aarch64-linux-android21" } - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } - { os: android, arch: amd64, ndk: "x86_64-linux-android21" } @@ -146,7 +142,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build if: matrix.os != 'android' @@ -285,6 +281,77 @@ jobs: with: name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }} path: "dist" + build_darwin: + name: Build Darwin binaries + if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' + runs-on: macos-latest + needs: + - calculate_version + strategy: + matrix: + include: + - { arch: amd64 } + - { arch: arm64 } + - { arch: amd64, legacy_go124: true, legacy_name: "macos-11" } + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + - name: Setup Go + if: ${{ ! matrix.legacy_go124 }} + uses: actions/setup-go@v5 + with: + go-version: ^1.25.3 + - name: Setup Go 1.24 + if: matrix.legacy_go124 + uses: actions/setup-go@v5 + with: + go-version: ~1.24.6 + - name: Set tag + run: |- + git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" + git tag v${{ needs.calculate_version.outputs.version }} -f + - name: Set build tags + run: | + set -xeuo pipefail + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Build + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: darwin + GOARCH: ${{ matrix.arch }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set name + run: |- + DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}" + if [[ -n "${{ matrix.legacy_name }}" ]]; then + DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}" + fi + echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" + - name: Archive + run: | + set -xeuo pipefail + cd dist + mkdir -p "${DIR_NAME}" + cp ../LICENSE "${DIR_NAME}" + cp sing-box "${DIR_NAME}" + tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}" + rm -r "${DIR_NAME}" + - name: Cleanup + run: rm dist/sing-box + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }} + path: "dist" build_android: name: Build Android if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' @@ -619,6 +686,7 @@ jobs: needs: - calculate_version - build + - build_darwin - build_android - build_apple steps: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6f84a899..f2b8a50b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -85,7 +85,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build run: | diff --git a/.goreleaser.fury.yaml b/.goreleaser.fury.yaml deleted file mode 100644 index 3763db01..00000000 --- a/.goreleaser.fury.yaml +++ /dev/null @@ -1,103 +0,0 @@ -project_name: sing-box -builds: - - id: main - main: ./cmd/sing-box - flags: - - -v - - -trimpath - ldflags: - - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} - - -s - - -buildid= - tags: - - with_gvisor - - with_quic - - with_dhcp - - with_wireguard - - with_utls - - with_acme - - with_clash_api - - with_tailscale - env: - - CGO_ENABLED=0 - targets: - - linux_386 - - linux_amd64_v1 - - linux_arm64 - - linux_arm_7 - - linux_s390x - - linux_riscv64 - - linux_mips64le - mod_timestamp: '{{ .CommitTimestamp }}' -snapshot: - name_template: "{{ .Version }}.{{ .ShortCommit }}" -nfpms: - - &template - id: package - package_name: sing-box - file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - builds: - - main - homepage: https://sing-box.sagernet.org/ - maintainer: nekohasekai - description: The universal proxy platform. - license: GPLv3 or later - formats: - - deb - - rpm - priority: extra - contents: - - src: release/config/config.json - dst: /etc/sing-box/config.json - type: "config|noreplace" - - - src: release/config/sing-box.service - dst: /usr/lib/systemd/system/sing-box.service - - src: release/config/sing-box@.service - dst: /usr/lib/systemd/system/sing-box@.service - - src: release/config/sing-box.sysusers - dst: /usr/lib/sysusers.d/sing-box.conf - - src: release/config/sing-box.rules - dst: /usr/share/polkit-1/rules.d/sing-box.rules - - src: release/config/sing-box-split-dns.xml - dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf - - - src: release/completions/sing-box.bash - dst: /usr/share/bash-completion/completions/sing-box.bash - - src: release/completions/sing-box.fish - dst: /usr/share/fish/vendor_completions.d/sing-box.fish - - src: release/completions/sing-box.zsh - dst: /usr/share/zsh/site-functions/_sing-box - - - src: LICENSE - dst: /usr/share/licenses/sing-box/LICENSE - deb: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - fields: - Bugs: https://github.com/SagerNet/sing-box/issues - rpm: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - conflicts: - - sing-box-beta - - id: package_beta - <<: *template - package_name: sing-box-beta - file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - formats: - - deb - - rpm - conflicts: - - sing-box -release: - disable: true -furies: - - account: sagernet - ids: - - package - disable: "{{ not (not .Prerelease) }}" - - account: sagernet - ids: - - package_beta - disable: "{{ not .Prerelease }}" diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 6ee53c5c..00000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,213 +0,0 @@ -version: 2 -project_name: sing-box -builds: - - &template - id: main - main: ./cmd/sing-box - flags: - - -v - - -trimpath - ldflags: - - -X github.com/sagernet/sing-box/constant.Version={{ .Version }} - - -s - - -buildid= - tags: - - with_gvisor - - with_quic - - with_dhcp - - with_wireguard - - with_utls - - with_acme - - with_clash_api - - with_tailscale - env: - - CGO_ENABLED=0 - - GOTOOLCHAIN=local - targets: - - linux_386 - - linux_amd64_v1 - - linux_arm64 - - linux_arm_6 - - linux_arm_7 - - linux_s390x - - linux_riscv64 - - linux_mips64le - - windows_amd64_v1 - - windows_386 - - windows_arm64 - - darwin_amd64_v1 - - darwin_arm64 - mod_timestamp: '{{ .CommitTimestamp }}' - - id: legacy - <<: *template - tags: - - with_gvisor - - with_quic - - with_dhcp - - with_wireguard - - with_utls - - with_acme - - with_clash_api - - with_tailscale - env: - - CGO_ENABLED=0 - - GOROOT={{ .Env.GOPATH }}/go_legacy - tool: "{{ .Env.GOPATH }}/go_legacy/bin/go" - targets: - - windows_amd64_v1 - - windows_386 - - id: android - <<: *template - env: - - CGO_ENABLED=1 - - GOTOOLCHAIN=local - overrides: - - goos: android - goarch: arm - goarm: 7 - env: - - CC=armv7a-linux-androideabi21-clang - - CXX=armv7a-linux-androideabi21-clang++ - - goos: android - goarch: arm64 - env: - - CC=aarch64-linux-android21-clang - - CXX=aarch64-linux-android21-clang++ - - goos: android - goarch: 386 - env: - - CC=i686-linux-android21-clang - - CXX=i686-linux-android21-clang++ - - goos: android - goarch: amd64 - goamd64: v1 - env: - - CC=x86_64-linux-android21-clang - - CXX=x86_64-linux-android21-clang++ - targets: - - android_arm_7 - - android_arm64 - - android_386 - - android_amd64 -archives: - - &template - id: archive - builds: - - main - - android - formats: - - tar.gz - format_overrides: - - goos: windows - formats: - - zip - wrap_in_directory: true - files: - - LICENSE - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - - id: archive-legacy - <<: *template - builds: - - legacy - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy' -nfpms: - - id: package - package_name: sing-box - file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' - builds: - - main - homepage: https://sing-box.sagernet.org/ - maintainer: nekohasekai - description: The universal proxy platform. - license: GPLv3 or later - formats: - - deb - - rpm - - archlinux -# - apk -# - ipk - priority: extra - contents: - - src: release/config/config.json - dst: /etc/sing-box/config.json - type: "config|noreplace" - - - src: release/config/sing-box.service - dst: /usr/lib/systemd/system/sing-box.service - - src: release/config/sing-box@.service - dst: /usr/lib/systemd/system/sing-box@.service - - src: release/config/sing-box.sysusers - dst: /usr/lib/sysusers.d/sing-box.conf - - src: release/config/sing-box.rules - dst: /usr/share/polkit-1/rules.d/sing-box.rules - - src: release/config/sing-box-split-dns.xml - dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf - - - src: release/completions/sing-box.bash - dst: /usr/share/bash-completion/completions/sing-box.bash - - src: release/completions/sing-box.fish - dst: /usr/share/fish/vendor_completions.d/sing-box.fish - - src: release/completions/sing-box.zsh - dst: /usr/share/zsh/site-functions/_sing-box - - - src: LICENSE - dst: /usr/share/licenses/sing-box/LICENSE - deb: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - fields: - Bugs: https://github.com/SagerNet/sing-box/issues - rpm: - signature: - key_file: "{{ .Env.NFPM_KEY_PATH }}" - overrides: - apk: - contents: - - src: release/config/config.json - dst: /etc/sing-box/config.json - type: config - - - src: release/config/sing-box.initd - dst: /etc/init.d/sing-box - - - src: release/completions/sing-box.bash - dst: /usr/share/bash-completion/completions/sing-box.bash - - src: release/completions/sing-box.fish - dst: /usr/share/fish/vendor_completions.d/sing-box.fish - - src: release/completions/sing-box.zsh - dst: /usr/share/zsh/site-functions/_sing-box - - - src: LICENSE - dst: /usr/share/licenses/sing-box/LICENSE - ipk: - contents: - - src: release/config/config.json - dst: /etc/sing-box/config.json - type: config - - - src: release/config/openwrt.init - dst: /etc/init.d/sing-box - - src: release/config/openwrt.conf - dst: /etc/config/sing-box -source: - enabled: false - name_template: '{{ .ProjectName }}-{{ .Version }}.source' - prefix_template: '{{ .ProjectName }}-{{ .Version }}/' -checksum: - disable: true - name_template: '{{ .ProjectName }}-{{ .Version }}.checksum' -signs: - - artifacts: checksum -release: - github: - owner: SagerNet - name: sing-box - draft: true - prerelease: auto - mode: replace - ids: - - archive - - package - skip_upload: true -partial: - by: target \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5273b2e7..5162d461 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN set -ex \ && export COMMIT=$(git rev-parse --short HEAD) \ && export VERSION=$(go run ./cmd/internal/read_tag) \ && go build -v -trimpath -tags \ - "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0" \ + "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" \ -o /go/bin/sing-box \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box diff --git a/Makefile b/Makefile index baeac709..2298e66e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0 +TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0 GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) diff --git a/constant/proxy.go b/constant/proxy.go index cf12c48d..a54a3a75 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -28,6 +28,7 @@ const ( TypeDERP = "derp" TypeResolved = "resolved" TypeSSMAPI = "ssm-api" + TypeCCM = "ccm" ) const ( diff --git a/docs/configuration/service/ccm.md b/docs/configuration/service/ccm.md new file mode 100644 index 00000000..e0ccfa1f --- /dev/null +++ b/docs/configuration/service/ccm.md @@ -0,0 +1,104 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.13.0" + +# CCM + +CCM (Claude Code Multiplexer) service is a multiplexing service that allows you to access your local Claude Code subscription remotely through custom tokens. + +It handles OAuth authentication with Claude's API on your local machine while allowing remote Claude Code to authenticate using Auth Tokens via the `ANTHROPIC_AUTH_TOKEN` environment variable. + +### Structure + +```json +{ + "type": "ccm", + + ... // Listen Fields + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### credential_path + +Path to the Claude Code OAuth credentials file. + +Defaults to `~/.claude/.credentials.json` if not specified. + +On macOS, credentials are read from the system keychain first, then fall back to the file if unavailable. + +Refreshed tokens are automatically written back to the same location. + +#### usages_path + +Path to the file for storing aggregated API usage statistics. + +Usage tracking is disabled if not specified. + +When enabled, the service tracks and saves comprehensive statistics including: +- Request counts +- Token usage (input, output, cache read, cache creation) +- Calculated costs in USD based on Claude API pricing + +Statistics are organized by model, context window (200k standard vs 1M premium), and optionally by user when authentication is enabled. + +The statistics file is automatically saved every minute and upon service shutdown. + +#### users + +List of authorized users for token authentication. + +If empty, no authentication is required. + +Claude Code authenticates by setting the `ANTHROPIC_AUTH_TOKEN` environment variable to their token value. + +#### headers + +Custom HTTP headers to send to the Claude API. + +These headers will override any existing headers with the same name. + +#### detour + +Outbound tag for connecting to the Claude API. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +### Example + +```json +{ + "services": [ + { + "type": "ccm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +Connect to the CCM service: + +```bash +export ANTHROPIC_BASE_URL="http://127.0.0.1:8080" +export ANTHROPIC_AUTH_TOKEN="sk-ant-ccm-auth-token-not-required-in-this-context" + +claude +``` diff --git a/docs/configuration/service/ccm.zh.md b/docs/configuration/service/ccm.zh.md new file mode 100644 index 00000000..fa5e5d51 --- /dev/null +++ b/docs/configuration/service/ccm.zh.md @@ -0,0 +1,104 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.13.0 起" + +# CCM + +CCM(Claude Code 多路复用器)服务是一个多路复用服务,允许您通过自定义令牌远程访问本地的 Claude Code 订阅。 + +它在本地机器上处理与 Claude API 的 OAuth 身份验证,同时允许远程 Claude Code 通过 `ANTHROPIC_AUTH_TOKEN` 环境变量使用认证令牌进行身份验证。 + +### 结构 + +```json +{ + "type": "ccm", + + ... // 监听字段 + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### credential_path + +Claude Code OAuth 凭据文件的路径。 + +如果未指定,默认使用 `~/.claude/.credentials.json`。 + +在 macOS 上,首先从系统钥匙串读取凭据,如果不可用则回退到文件。 + +刷新的令牌会自动写回相同位置。 + +#### usages_path + +用于存储聚合 API 使用统计信息的文件路径。 + +如果未指定,使用跟踪将被禁用。 + +启用后,服务会跟踪并保存全面的统计信息,包括: +- 请求计数 +- 令牌使用量(输入、输出、缓存读取、缓存创建) +- 基于 Claude API 定价计算的美元成本 + +统计信息按模型、上下文窗口(200k 标准版 vs 1M 高级版)以及可选的用户(启用身份验证时)进行组织。 + +统计文件每分钟自动保存一次,并在服务关闭时保存。 + +#### users + +用于令牌身份验证的授权用户列表。 + +如果为空,则不需要身份验证。 + +Claude Code 通过设置 `ANTHROPIC_AUTH_TOKEN` 环境变量为其令牌值进行身份验证。 + +#### headers + +发送到 Claude API 的自定义 HTTP 头。 + +这些头会覆盖同名的现有头。 + +#### detour + +用于连接 Claude API 的出站标签。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 + +### 示例 + +```json +{ + "services": [ + { + "type": "ccm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +连接到 CCM 服务: + +```bash +export ANTHROPIC_BASE_URL="http://127.0.0.1:8080" +export ANTHROPIC_AUTH_TOKEN="sk-ant-ccm-auth-token-not-required-in-this-context" + +claude +``` diff --git a/docs/configuration/service/index.md b/docs/configuration/service/index.md index 87a0042d..2bd1a4a3 100644 --- a/docs/configuration/service/index.md +++ b/docs/configuration/service/index.md @@ -23,6 +23,7 @@ icon: material/new-box | Type | Format | |------------|------------------------| +| `ccm` | [CCM](./ccm) | | `derp` | [DERP](./derp) | | `resolved` | [Resolved](./resolved) | | `ssm-api` | [SSM API](./ssm-api) | diff --git a/docs/configuration/service/index.zh.md b/docs/configuration/service/index.zh.md index d534aa85..b4a73eda 100644 --- a/docs/configuration/service/index.zh.md +++ b/docs/configuration/service/index.zh.md @@ -23,6 +23,7 @@ icon: material/new-box | 类型 | 格式 | |-----------|------------------------| +| `ccm` | [CCM](./ccm) | | `derp` | [DERP](./derp) | | `resolved`| [Resolved](./resolved) | | `ssm-api` | [SSM API](./ssm-api) | diff --git a/go.mod b/go.mod index 5b1bff87..c34abc0e 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/sagernet/sing-box go 1.24.7 require ( + github.com/anthropics/anthropic-sdk-go v1.14.0 github.com/anytls/sing-anytls v0.0.11 github.com/caddyserver/certmagic v0.23.0 github.com/coder/websocket v1.8.13 @@ -13,6 +14,7 @@ require ( github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 github.com/gofrs/uuid/v5 v5.3.2 github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f + github.com/keybase/go-keychain v0.0.1 github.com/libdns/alidns v1.0.5-libdns.v1.beta1 github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 github.com/logrusorgru/aurora v2.0.3+incompatible @@ -113,6 +115,10 @@ require ( github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect diff --git a/go.sum b/go.sum index c420641a..ebeb026e 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/anthropics/anthropic-sdk-go v1.14.0 h1:EzNQvnZlaDHe2UPkoUySDz3ixRgNbwKdH8KtFpv7pi4= +github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= @@ -94,6 +96,8 @@ github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBe github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= @@ -209,6 +213,16 @@ github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= diff --git a/include/ccm.go b/include/ccm.go new file mode 100644 index 00000000..a7520148 --- /dev/null +++ b/include/ccm.go @@ -0,0 +1,12 @@ +//go:build with_ccm && (!darwin || cgo) + +package include + +import ( + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/service/ccm" +) + +func registerCCMService(registry *service.Registry) { + ccm.RegisterService(registry) +} diff --git a/include/ccm_stub.go b/include/ccm_stub.go new file mode 100644 index 00000000..eac29eeb --- /dev/null +++ b/include/ccm_stub.go @@ -0,0 +1,20 @@ +//go:build !with_ccm + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerCCMService(registry *service.Registry) { + service.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { + return nil, E.New(`CCM is not included in this build, rebuild with -tags with_CCM`) + }) +} diff --git a/include/ccm_stub_darwin.go b/include/ccm_stub_darwin.go new file mode 100644 index 00000000..f2ad7381 --- /dev/null +++ b/include/ccm_stub_darwin.go @@ -0,0 +1,20 @@ +//go:build with_ccm && darwin && !cgo + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerCCMService(registry *service.Registry) { + service.Register[option.CCMServiceOptions](registry, C.TypeCCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { + return nil, E.New(`CCM requires CGO on darwin, rebuild with CGO_ENABLED=1`) + }) +} diff --git a/include/registry.go b/include/registry.go index 94d56db1..9965bc5e 100644 --- a/include/registry.go +++ b/include/registry.go @@ -134,6 +134,7 @@ func ServiceRegistry() *service.Registry { ssmapi.RegisterService(registry) registerDERPService(registry) + registerCCMService(registry) return registry } diff --git a/option/ccm.go b/option/ccm.go new file mode 100644 index 00000000..c916aaf2 --- /dev/null +++ b/option/ccm.go @@ -0,0 +1,20 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type CCMServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + CredentialPath string `json:"credential_path,omitempty"` + Users []CCMUser `json:"users,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + Detour string `json:"detour,omitempty"` + UsagesPath string `json:"usages_path,omitempty"` +} + +type CCMUser struct { + Name string `json:"name,omitempty"` + Token string `json:"token,omitempty"` +} diff --git a/release/local/common.sh b/release/local/common.sh new file mode 100755 index 00000000..d24bba47 --- /dev/null +++ b/release/local/common.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +BINARY_NAME="sing-box" + +INSTALL_BIN_PATH="/usr/local/bin" +INSTALL_CONFIG_PATH="/usr/local/etc/sing-box" +INSTALL_DATA_PATH="/var/lib/sing-box" +SYSTEMD_SERVICE_PATH="/etc/systemd/system" + +DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" + +setup_environment() { + if [ -d /usr/local/go ]; then + export PATH="$PATH:/usr/local/go/bin" + fi + + if ! command -v go &> /dev/null; then + echo "Error: Go is not installed or not in PATH" + echo "Run install_go.sh to install Go" + exit 1 + fi +} + +get_build_tags() { + local extra_tags="$1" + if [ -n "$extra_tags" ]; then + echo "${DEFAULT_BUILD_TAGS},${extra_tags}" + else + echo "${DEFAULT_BUILD_TAGS}" + fi +} + +get_version() { + cd "$PROJECT_DIR" + GOHOSTOS=$(go env GOHOSTOS) + GOHOSTARCH=$(go env GOHOSTARCH) + CGO_ENABLED=0 GOOS=$GOHOSTOS GOARCH=$GOHOSTARCH go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest +} + +get_ldflags() { + local version + version=$(get_version) + echo "-X 'github.com/sagernet/sing-box/constant.Version=${version}' -s -w -buildid= -checklinkname=0" +} + +build_sing_box() { + local tags="$1" + local ldflags + ldflags=$(get_ldflags) + + echo "Building sing-box with tags: $tags" + cd "$PROJECT_DIR" + export GOTOOLCHAIN=local + go install -v -trimpath -ldflags "$ldflags" -tags "$tags" ./cmd/sing-box +} + +install_binary() { + local gopath + gopath=$(go env GOPATH) + echo "Installing binary to $INSTALL_BIN_PATH/$BINARY_NAME" + sudo cp "${gopath}/bin/${BINARY_NAME}" "${INSTALL_BIN_PATH}/" +} + +setup_config() { + echo "Setting up configuration" + sudo mkdir -p "$INSTALL_CONFIG_PATH" + if [ ! -f "$INSTALL_CONFIG_PATH/config.json" ]; then + sudo cp "$PROJECT_DIR/release/config/config.json" "$INSTALL_CONFIG_PATH/config.json" + echo "Default config installed to $INSTALL_CONFIG_PATH/config.json" + else + echo "Config already exists at $INSTALL_CONFIG_PATH/config.json (not overwriting)" + fi +} + +setup_systemd() { + echo "Setting up systemd service" + sudo cp "$SCRIPT_DIR/sing-box.service" "$SYSTEMD_SERVICE_PATH/" + sudo systemctl daemon-reload +} + +stop_service() { + if systemctl is-active --quiet sing-box; then + echo "Stopping sing-box service" + sudo systemctl stop sing-box + fi +} + +start_service() { + echo "Starting sing-box service" + sudo systemctl start sing-box +} + +restart_service() { + echo "Restarting sing-box service" + sudo systemctl restart sing-box +} diff --git a/release/local/debug.sh b/release/local/debug.sh index d6bd3057..d8651999 100755 --- a/release/local/debug.sh +++ b/release/local/debug.sh @@ -2,21 +2,25 @@ set -e -o pipefail -if [ -d /usr/local/go ]; then - export PATH="$PATH:/usr/local/go/bin" -fi +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +setup_environment -pushd $PROJECT +echo "Updating sing-box from git repository..." +cd "$PROJECT_DIR" git fetch git reset FETCH_HEAD --hard git clean -fdx -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_acme,debug ./cmd/sing-box -popd -sudo systemctl stop sing-box -sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ -sudo systemctl start sing-box +BUILD_TAGS=$(get_build_tags "debug") + +build_sing_box "$BUILD_TAGS" + +stop_service +install_binary +start_service + +echo "" +echo "Following service logs (Ctrl+C to exit)..." sudo journalctl -u sing-box --output cat -f diff --git a/release/local/install.sh b/release/local/install.sh index 24e9d006..d5bf94fc 100755 --- a/release/local/install.sh +++ b/release/local/install.sh @@ -2,19 +2,18 @@ set -e -o pipefail -if [ -d /usr/local/go ]; then - export PATH="$PATH:/usr/local/go/bin" -fi +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +setup_environment -pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box -popd +BUILD_TAGS=$(get_build_tags) -sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ -sudo mkdir -p /usr/local/etc/sing-box -sudo cp $PROJECT/release/config/config.json /usr/local/etc/sing-box/config.json -sudo cp $DIR/sing-box.service /etc/systemd/system -sudo systemctl daemon-reload +build_sing_box "$BUILD_TAGS" +install_binary +setup_config +setup_systemd + +echo "" +echo "Installation complete!" +echo "To enable and start the service, run: $SCRIPT_DIR/enable.sh" diff --git a/release/local/reinstall.sh b/release/local/reinstall.sh index 71d07109..1daaa181 100755 --- a/release/local/reinstall.sh +++ b/release/local/reinstall.sh @@ -2,17 +2,18 @@ set -e -o pipefail -if [ -d /usr/local/go ]; then - export PATH="$PATH:/usr/local/go/bin" -fi +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +setup_environment -pushd $PROJECT -go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box -popd +BUILD_TAGS=$(get_build_tags) -sudo systemctl stop sing-box -sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ -sudo systemctl start sing-box +build_sing_box "$BUILD_TAGS" + +stop_service +install_binary +start_service + +echo "" +echo "Reinstallation complete!" diff --git a/release/local/uninstall.sh b/release/local/uninstall.sh index d40107ba..b9c89ab0 100755 --- a/release/local/uninstall.sh +++ b/release/local/uninstall.sh @@ -1,8 +1,30 @@ #!/usr/bin/env bash -sudo systemctl stop sing-box -sudo rm -rf /var/lib/sing-box -sudo rm -rf /usr/local/bin/sing-box -sudo rm -rf /usr/local/etc/sing-box -sudo rm -rf /etc/systemd/system/sing-box.service +set -e -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +echo "Uninstalling sing-box..." + +if systemctl is-active --quiet sing-box 2>/dev/null; then + echo "Stopping sing-box service..." + sudo systemctl stop sing-box +fi + +if systemctl is-enabled --quiet sing-box 2>/dev/null; then + echo "Disabling sing-box service..." + sudo systemctl disable sing-box +fi + +echo "Removing files..." +sudo rm -rf "$INSTALL_DATA_PATH" +sudo rm -rf "$INSTALL_BIN_PATH/$BINARY_NAME" +sudo rm -rf "$INSTALL_CONFIG_PATH" +sudo rm -rf "$SYSTEMD_SERVICE_PATH/sing-box.service" + +echo "Reloading systemd..." sudo systemctl daemon-reload + +echo "" +echo "Uninstallation complete!" diff --git a/release/local/update.sh b/release/local/update.sh index 86ea315d..2331d270 100755 --- a/release/local/update.sh +++ b/release/local/update.sh @@ -2,13 +2,15 @@ set -e -o pipefail -DIR=$(dirname "$0") -PROJECT=$DIR/../.. +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/common.sh" -pushd $PROJECT +echo "Updating sing-box from git repository..." +cd "$PROJECT_DIR" git fetch git reset FETCH_HEAD --hard git clean -fdx -popd -$DIR/reinstall.sh \ No newline at end of file +echo "" +echo "Running reinstall..." +exec "$SCRIPT_DIR/reinstall.sh" \ No newline at end of file diff --git a/service/ccm/credential.go b/service/ccm/credential.go new file mode 100644 index 00000000..5fd79721 --- /dev/null +++ b/service/ccm/credential.go @@ -0,0 +1,136 @@ +package ccm + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "os" + "os/user" + "path/filepath" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + oauth2ClientID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e" + oauth2TokenURL = "https://console.anthropic.com/v1/oauth/token" + claudeAPIBaseURL = "https://api.anthropic.com" + tokenRefreshBufferMs = 60000 + anthropicBetaOAuthValue = "oauth-2025-04-20" +) + +func getRealUser() (*user.User, error) { + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + sudoUserInfo, err := user.Lookup(sudoUser) + if err == nil { + return sudoUserInfo, nil + } + } + return user.Current() +} + +func getDefaultCredentialsPath() (string, error) { + userInfo, err := getRealUser() + if err != nil { + return "", err + } + return filepath.Join(userInfo.HomeDir, ".claude", ".credentials.json"), nil +} + +func readCredentialsFromFile(path string) (*oauthCredentials, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var credentialsContainer struct { + ClaudeAIAuth *oauthCredentials `json:"claudeAiOauth,omitempty"` + } + err = json.Unmarshal(data, &credentialsContainer) + if err != nil { + return nil, err + } + if credentialsContainer.ClaudeAIAuth == nil { + return nil, E.New("claudeAiOauth field not found in credentials") + } + return credentialsContainer.ClaudeAIAuth, nil +} + +func writeCredentialsToFile(oauthCredentials *oauthCredentials, path string) error { + data, err := json.MarshalIndent(map[string]any{ + "claudeAiOauth": oauthCredentials, + }, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0o600) +} + +type oauthCredentials struct { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + ExpiresAt int64 `json:"expiresAt"` + Scopes []string `json:"scopes,omitempty"` + SubscriptionType string `json:"subscriptionType,omitempty"` + IsMax bool `json:"isMax,omitempty"` +} + +func (c *oauthCredentials) needsRefresh() bool { + if c.ExpiresAt == 0 { + return false + } + return time.Now().UnixMilli() >= c.ExpiresAt-tokenRefreshBufferMs +} + +func refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) { + if credentials.RefreshToken == "" { + return nil, E.New("refresh token is empty") + } + + requestBody, err := json.Marshal(map[string]string{ + "grant_type": "refresh_token", + "refresh_token": credentials.RefreshToken, + "client_id": oauth2ClientID, + }) + if err != nil { + return nil, E.Cause(err, "marshal request") + } + + request, err := http.NewRequest("POST", oauth2TokenURL, bytes.NewReader(requestBody)) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + + response, err := httpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + body, _ := io.ReadAll(response.Body) + return nil, E.New("refresh failed: ", response.Status, " ", string(body)) + } + + var tokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` + } + err = json.NewDecoder(response.Body).Decode(&tokenResponse) + if err != nil { + return nil, E.Cause(err, "decode response") + } + + newCredentials := *credentials + newCredentials.AccessToken = tokenResponse.AccessToken + if tokenResponse.RefreshToken != "" { + newCredentials.RefreshToken = tokenResponse.RefreshToken + } + newCredentials.ExpiresAt = time.Now().UnixMilli() + int64(tokenResponse.ExpiresIn)*1000 + + return &newCredentials, nil +} diff --git a/service/ccm/credential_darwin.go b/service/ccm/credential_darwin.go new file mode 100644 index 00000000..24047b85 --- /dev/null +++ b/service/ccm/credential_darwin.go @@ -0,0 +1,116 @@ +//go:build darwin && cgo + +package ccm + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/keybase/go-keychain" +) + +func getKeychainServiceName() string { + configDirectory := os.Getenv("CLAUDE_CONFIG_DIR") + if configDirectory == "" { + return "Claude Code-credentials" + } + + userInfo, err := getRealUser() + if err != nil { + return "Claude Code-credentials" + } + defaultConfigDirectory := filepath.Join(userInfo.HomeDir, ".claude") + if configDirectory == defaultConfigDirectory { + return "Claude Code-credentials" + } + + hash := sha256.Sum256([]byte(configDirectory)) + return "Claude Code-credentials-" + hex.EncodeToString(hash[:])[:8] +} + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath != "" { + return readCredentialsFromFile(customPath) + } + + userInfo, err := getRealUser() + if err == nil { + query := keychain.NewItem() + query.SetSecClass(keychain.SecClassGenericPassword) + query.SetService(getKeychainServiceName()) + query.SetAccount(userInfo.Username) + query.SetMatchLimit(keychain.MatchLimitOne) + query.SetReturnData(true) + + results, err := keychain.QueryItem(query) + if err == nil && len(results) == 1 { + var container struct { + ClaudeAIAuth *oauthCredentials `json:"claudeAiOauth,omitempty"` + } + unmarshalErr := json.Unmarshal(results[0].Data, &container) + if unmarshalErr == nil && container.ClaudeAIAuth != nil { + return container.ClaudeAIAuth, nil + } + } + if err != nil && err != keychain.ErrorItemNotFound { + return nil, E.Cause(err, "query keychain") + } + } + + defaultPath, err := getDefaultCredentialsPath() + if err != nil { + return nil, err + } + return readCredentialsFromFile(defaultPath) +} + +func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { + if customPath != "" { + return writeCredentialsToFile(oauthCredentials, customPath) + } + + userInfo, err := getRealUser() + if err == nil { + data, err := json.Marshal(map[string]any{"claudeAiOauth": oauthCredentials}) + if err == nil { + serviceName := getKeychainServiceName() + item := keychain.NewItem() + item.SetSecClass(keychain.SecClassGenericPassword) + item.SetService(serviceName) + item.SetAccount(userInfo.Username) + item.SetData(data) + item.SetAccessible(keychain.AccessibleWhenUnlocked) + + err = keychain.AddItem(item) + if err == nil { + return nil + } + + if err == keychain.ErrorDuplicateItem { + query := keychain.NewItem() + query.SetSecClass(keychain.SecClassGenericPassword) + query.SetService(serviceName) + query.SetAccount(userInfo.Username) + + updateItem := keychain.NewItem() + updateItem.SetData(data) + + updateErr := keychain.UpdateItem(query, updateItem) + if updateErr == nil { + return nil + } + } + } + } + + defaultPath, err := getDefaultCredentialsPath() + if err != nil { + return err + } + return writeCredentialsToFile(oauthCredentials, defaultPath) +} diff --git a/service/ccm/credential_other.go b/service/ccm/credential_other.go new file mode 100644 index 00000000..11888b50 --- /dev/null +++ b/service/ccm/credential_other.go @@ -0,0 +1,25 @@ +//go:build !darwin + +package ccm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(oauthCredentials, customPath) +} diff --git a/service/ccm/service.go b/service/ccm/service.go new file mode 100644 index 00000000..84074694 --- /dev/null +++ b/service/ccm/service.go @@ -0,0 +1,541 @@ +package ccm + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "mime" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" + "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/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + + "github.com/anthropics/anthropic-sdk-go" + "github.com/go-chi/chi/v5" + "golang.org/x/net/http2" +) + +const ( + contextWindowStandard = 200000 + contextWindowPremium = 1000000 + premiumContextThreshold = 200000 +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.CCMServiceOptions](registry, C.TypeCCM, NewService) +} + +type errorResponse struct { + Type string `json:"type"` + Error errorDetails `json:"error"` + RequestID string `json:"request_id,omitempty"` +} + +type errorDetails struct { + Type string `json:"type"` + Message string `json:"message"` +} + +func writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + json.NewEncoder(w).Encode(errorResponse{ + Type: "error", + Error: errorDetails{ + Type: errorType, + Message: message, + }, + RequestID: r.Header.Get("Request-Id"), + }) +} + +func isHopByHopHeader(header string) bool { + switch strings.ToLower(header) { + case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host": + return true + default: + return false + } +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger log.ContextLogger + credentialPath string + credentials *oauthCredentials + users []option.CCMUser + httpClient *http.Client + httpHeaders http.Header + listener *listener.Listener + tlsConfig tls.ServerConfig + httpServer *http.Server + userManager *UserManager + accessMutex sync.RWMutex + usageTracker *AggregatedUsage + trackingGroup sync.WaitGroup + shuttingDown bool +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { + serviceDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: option.DialerOptions{ + Detour: options.Detour, + }, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "create dialer") + } + + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + + userManager := &UserManager{ + tokenMap: make(map[string]string), + } + + var usageTracker *AggregatedUsage + if options.UsagesPath != "" { + usageTracker = &AggregatedUsage{ + LastUpdated: time.Now(), + Combinations: make([]CostCombination, 0), + filePath: options.UsagesPath, + logger: logger, + } + } + + service := &Service{ + Adapter: boxService.NewAdapter(C.TypeCCM, tag), + ctx: ctx, + logger: logger, + credentialPath: options.CredentialPath, + users: options.Users, + httpClient: httpClient, + httpHeaders: options.Headers.Build(), + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + userManager: userManager, + usageTracker: usageTracker, + } + + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + service.tlsConfig = tlsConfig + } + + return service, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + s.userManager.UpdateUsers(s.users) + + credentials, err := platformReadCredentials(s.credentialPath) + if err != nil { + return E.Cause(err, "read credentials") + } + s.credentials = credentials + + if s.usageTracker != nil { + err = s.usageTracker.Load() + if err != nil { + s.logger.Warn("load usage statistics: ", err) + } + } + + router := chi.NewRouter() + router.Mount("/", s) + + s.httpServer = &http.Server{Handler: router} + + if s.tlsConfig != nil { + err = s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + + tcpListener, err := s.listener.ListenTCP() + if err != nil { + return err + } + + if s.tlsConfig != nil { + if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { + s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) + } + + go func() { + serveErr := s.httpServer.Serve(tcpListener) + if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { + s.logger.Error("serve error: ", serveErr) + } + }() + + return nil +} + +func (s *Service) getAccessToken() (string, error) { + s.accessMutex.RLock() + if !s.credentials.needsRefresh() { + token := s.credentials.AccessToken + s.accessMutex.RUnlock() + return token, nil + } + s.accessMutex.RUnlock() + + s.accessMutex.Lock() + defer s.accessMutex.Unlock() + + if !s.credentials.needsRefresh() { + return s.credentials.AccessToken, nil + } + + newCredentials, err := refreshToken(s.httpClient, s.credentials) + if err != nil { + return "", err + } + + s.credentials = newCredentials + + err = platformWriteCredentials(newCredentials, s.credentialPath) + if err != nil { + s.logger.Warn("persist refreshed token: ", err) + } + + return newCredentials.AccessToken, nil +} + +func detectContextWindow(betaHeader string, inputTokens int64) int { + if inputTokens > premiumContextThreshold { + features := strings.Split(betaHeader, ",") + for _, feature := range features { + if strings.TrimSpace(feature) == "context-1m" { + return contextWindowPremium + } + } + } + return contextWindowStandard +} + +func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.Path, "/v1/") { + writeJSONError(w, r, http.StatusNotFound, "not_found_error", "Not found") + return + } + + var username string + if len(s.users) > 0 { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": missing Authorization header") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") + return + } + clientToken := strings.TrimPrefix(authHeader, "Bearer ") + if clientToken == authHeader { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": invalid Authorization format") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") + return + } + var ok bool + username, ok = s.userManager.Authenticate(clientToken) + if !ok { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": unknown key: ", clientToken) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") + return + } + } + + var requestModel string + var messagesCount int + + if s.usageTracker != nil && r.Body != nil { + bodyBytes, err := io.ReadAll(r.Body) + if err == nil { + var request struct { + Model string `json:"model"` + Messages []anthropic.MessageParam `json:"messages"` + } + err := json.Unmarshal(bodyBytes, &request) + if err == nil { + requestModel = request.Model + messagesCount = len(request.Messages) + } + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + } + + accessToken, err := s.getAccessToken() + if err != nil { + s.logger.Error("get access token: ", err) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "Authentication failed") + return + } + + proxyURL := claudeAPIBaseURL + r.URL.RequestURI() + proxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body) + if err != nil { + s.logger.Error("create proxy request: ", err) + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "Internal server error") + return + } + + for key, values := range r.Header { + if !isHopByHopHeader(key) && key != "Authorization" { + proxyRequest.Header[key] = values + } + } + + anthropicBetaHeader := proxyRequest.Header.Get("anthropic-beta") + if anthropicBetaHeader != "" { + proxyRequest.Header.Set("anthropic-beta", anthropicBetaOAuthValue+","+anthropicBetaHeader) + } else { + proxyRequest.Header.Set("anthropic-beta", anthropicBetaOAuthValue) + } + + for key, values := range s.httpHeaders { + proxyRequest.Header.Del(key) + proxyRequest.Header[key] = values + } + + proxyRequest.Header.Set("Authorization", "Bearer "+accessToken) + + response, err := s.httpClient.Do(proxyRequest) + if err != nil { + writeJSONError(w, r, http.StatusBadGateway, "api_error", err.Error()) + return + } + defer response.Body.Close() + + for key, values := range response.Header { + if !isHopByHopHeader(key) { + w.Header()[key] = values + } + } + w.WriteHeader(response.StatusCode) + + if s.usageTracker != nil && response.StatusCode == http.StatusOK { + s.handleResponseWithTracking(w, response, requestModel, anthropicBetaHeader, messagesCount, username) + } else { + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + if err == nil && mediaType != "text/event-stream" { + _, _ = io.Copy(w, response.Body) + return + } + flusher, ok := w.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + buffer := make([]byte, buf.BufferSize) + for { + n, err := response.Body.Read(buffer) + if n > 0 { + _, writeError := w.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + if err != nil { + return + } + } + } +} + +func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, requestModel string, anthropicBetaHeader string, messagesCount int, username string) { + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + isStreaming := err == nil && mediaType == "text/event-stream" + + if !isStreaming { + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + s.logger.Error("read response body: ", err) + return + } + + var message anthropic.Message + var usage anthropic.Usage + var responseModel string + err = json.Unmarshal(bodyBytes, &message) + if err == nil { + responseModel = string(message.Model) + usage = message.Usage + } + if responseModel == "" { + responseModel = requestModel + } + + if usage.InputTokens > 0 || usage.OutputTokens > 0 { + if responseModel != "" { + contextWindow := detectContextWindow(anthropicBetaHeader, usage.InputTokens) + s.usageTracker.AddUsage( + responseModel, + contextWindow, + messagesCount, + usage.InputTokens, + usage.OutputTokens, + usage.CacheReadInputTokens, + usage.CacheCreationInputTokens, + username, + ) + } + } + + _, _ = writer.Write(bodyBytes) + return + } + + flusher, ok := writer.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + + var accumulatedUsage anthropic.Usage + var responseModel string + buffer := make([]byte, buf.BufferSize) + var leftover []byte + + for { + n, err := response.Body.Read(buffer) + if n > 0 { + data := append(leftover, buffer[:n]...) + lines := bytes.Split(data, []byte("\n")) + + if err == nil { + leftover = lines[len(lines)-1] + lines = lines[:len(lines)-1] + } else { + leftover = nil + } + + for _, line := range lines { + line = bytes.TrimSpace(line) + if len(line) == 0 { + continue + } + + if bytes.HasPrefix(line, []byte("data: ")) { + eventData := bytes.TrimPrefix(line, []byte("data: ")) + if bytes.Equal(eventData, []byte("[DONE]")) { + continue + } + + var event anthropic.MessageStreamEventUnion + err := json.Unmarshal(eventData, &event) + if err != nil { + continue + } + switch event.Type { + case "message_start": + messageStart := event.AsMessageStart() + if messageStart.Message.Model != "" { + responseModel = string(messageStart.Message.Model) + } + if messageStart.Message.Usage.InputTokens > 0 { + accumulatedUsage.InputTokens = messageStart.Message.Usage.InputTokens + accumulatedUsage.CacheReadInputTokens = messageStart.Message.Usage.CacheReadInputTokens + accumulatedUsage.CacheCreationInputTokens = messageStart.Message.Usage.CacheCreationInputTokens + } + case "message_delta": + messageDelta := event.AsMessageDelta() + if messageDelta.Usage.OutputTokens > 0 { + accumulatedUsage.OutputTokens = messageDelta.Usage.OutputTokens + } + } + } + } + + _, writeError := writer.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + + if err != nil { + if responseModel == "" { + responseModel = requestModel + } + + if accumulatedUsage.InputTokens > 0 || accumulatedUsage.OutputTokens > 0 { + if responseModel != "" { + contextWindow := detectContextWindow(anthropicBetaHeader, accumulatedUsage.InputTokens) + s.usageTracker.AddUsage( + responseModel, + contextWindow, + messagesCount, + accumulatedUsage.InputTokens, + accumulatedUsage.OutputTokens, + accumulatedUsage.CacheReadInputTokens, + accumulatedUsage.CacheCreationInputTokens, + username, + ) + } + } + return + } + } +} + +func (s *Service) Close() error { + err := common.Close( + common.PtrOrNil(s.httpServer), + common.PtrOrNil(s.listener), + s.tlsConfig, + ) + + if s.usageTracker != nil { + s.usageTracker.cancelPendingSave() + saveErr := s.usageTracker.Save() + if saveErr != nil { + s.logger.Error("save usage statistics: ", saveErr) + } + } + + return err +} diff --git a/service/ccm/service_usage.go b/service/ccm/service_usage.go new file mode 100644 index 00000000..53ae4658 --- /dev/null +++ b/service/ccm/service_usage.go @@ -0,0 +1,407 @@ +package ccm + +import ( + "encoding/json" + "math" + "os" + "regexp" + "sync" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" +) + +type UsageStats struct { + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` +} + +type CostCombination struct { + Model string `json:"model"` + ContextWindow int `json:"context_window"` + Total UsageStats `json:"total"` + ByUser map[string]UsageStats `json:"by_user"` +} + +type AggregatedUsage struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + mutex sync.Mutex + filePath string + logger log.ContextLogger + lastSaveTime time.Time + pendingSave bool + saveTimer *time.Timer + saveMutex sync.Mutex +} + +type UsageStatsJSON struct { + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CostUSD float64 `json:"cost_usd"` +} + +type CostCombinationJSON struct { + Model string `json:"model"` + ContextWindow int `json:"context_window"` + Total UsageStatsJSON `json:"total"` + ByUser map[string]UsageStatsJSON `json:"by_user"` +} + +type CostsSummaryJSON struct { + TotalUSD float64 `json:"total_usd"` + ByUser map[string]float64 `json:"by_user"` +} + +type AggregatedUsageJSON struct { + LastUpdated time.Time `json:"last_updated"` + Costs CostsSummaryJSON `json:"costs"` + Combinations []CostCombinationJSON `json:"combinations"` +} + +type ModelPricing struct { + InputPrice float64 + OutputPrice float64 + CacheReadPrice float64 + CacheWritePrice float64 +} + +type modelFamily struct { + pattern *regexp.Regexp + standardPricing ModelPricing + premiumPricing *ModelPricing +} + +var ( + opus4Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice: 18.75, + } + + sonnet4StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice: 3.75, + } + + sonnet4PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice: 7.5, + } + + haiku4Pricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice: 1.25, + } + + haiku35Pricing = ModelPricing{ + InputPrice: 0.8, + OutputPrice: 4.0, + CacheReadPrice: 0.08, + CacheWritePrice: 1.0, + } + + sonnet35Pricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice: 3.75, + } + + modelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^claude-(?:opus-4-|4-opus-|opus-4-1-)`), + standardPricing: opus4Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-3-7-sonnet-`), + standardPricing: sonnet4StandardPricing, + premiumPricing: &sonnet4PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-|4-sonnet-)`), + standardPricing: sonnet4StandardPricing, + premiumPricing: &sonnet4PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-haiku-4-`), + standardPricing: haiku4Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-3-5-haiku-`), + standardPricing: haiku35Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-3-5-sonnet-`), + standardPricing: sonnet35Pricing, + premiumPricing: nil, + }, + } +) + +func getPricing(model string, contextWindow int) ModelPricing { + isPremium := contextWindow >= contextWindowPremium + + for _, family := range modelFamilies { + if family.pattern.MatchString(model) { + if isPremium && family.premiumPricing != nil { + return *family.premiumPricing + } + return family.standardPricing + } + } + + return sonnet4StandardPricing +} + +func calculateCost(stats UsageStats, model string, contextWindow int) float64 { + pricing := getPricing(model, contextWindow) + + cost := (float64(stats.InputTokens)*pricing.InputPrice + + float64(stats.OutputTokens)*pricing.OutputPrice + + float64(stats.CacheReadInputTokens)*pricing.CacheReadPrice + + float64(stats.CacheCreationInputTokens)*pricing.CacheWritePrice) / 1_000_000 + + return math.Round(cost*100) / 100 +} + +func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { + u.mutex.Lock() + defer u.mutex.Unlock() + + result := &AggregatedUsageJSON{ + LastUpdated: u.LastUpdated, + Combinations: make([]CostCombinationJSON, len(u.Combinations)), + Costs: CostsSummaryJSON{ + TotalUSD: 0, + ByUser: make(map[string]float64), + }, + } + + for i, combo := range u.Combinations { + totalCost := calculateCost(combo.Total, combo.Model, combo.ContextWindow) + + result.Costs.TotalUSD += totalCost + + comboJSON := CostCombinationJSON{ + Model: combo.Model, + ContextWindow: combo.ContextWindow, + Total: UsageStatsJSON{ + RequestCount: combo.Total.RequestCount, + MessagesCount: combo.Total.MessagesCount, + InputTokens: combo.Total.InputTokens, + OutputTokens: combo.Total.OutputTokens, + CacheReadInputTokens: combo.Total.CacheReadInputTokens, + CacheCreationInputTokens: combo.Total.CacheCreationInputTokens, + CostUSD: totalCost, + }, + ByUser: make(map[string]UsageStatsJSON), + } + + for user, userStats := range combo.ByUser { + userCost := calculateCost(userStats, combo.Model, combo.ContextWindow) + result.Costs.ByUser[user] += userCost + + comboJSON.ByUser[user] = UsageStatsJSON{ + RequestCount: userStats.RequestCount, + MessagesCount: userStats.MessagesCount, + InputTokens: userStats.InputTokens, + OutputTokens: userStats.OutputTokens, + CacheReadInputTokens: userStats.CacheReadInputTokens, + CacheCreationInputTokens: userStats.CacheCreationInputTokens, + CostUSD: userCost, + } + } + + result.Combinations[i] = comboJSON + } + + result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100 + for user, cost := range result.Costs.ByUser { + result.Costs.ByUser[user] = math.Round(cost*100) / 100 + } + + return result +} + +func (u *AggregatedUsage) Load() error { + u.mutex.Lock() + defer u.mutex.Unlock() + + data, err := os.ReadFile(u.filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + var temp struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + } + + err = json.Unmarshal(data, &temp) + if err != nil { + return err + } + + u.LastUpdated = temp.LastUpdated + u.Combinations = temp.Combinations + + for i := range u.Combinations { + if u.Combinations[i].ByUser == nil { + u.Combinations[i].ByUser = make(map[string]UsageStats) + } + } + + return nil +} + +func (u *AggregatedUsage) Save() error { + jsonData := u.ToJSON() + + data, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return err + } + + tmpFile := u.filePath + ".tmp" + err = os.WriteFile(tmpFile, data, 0o644) + if err != nil { + return err + } + defer os.Remove(tmpFile) + err = os.Rename(tmpFile, u.filePath) + if err == nil { + u.saveMutex.Lock() + u.lastSaveTime = time.Now() + u.saveMutex.Unlock() + } + return err +} + +func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCount int, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens int64, user string) error { + if model == "" { + return E.New("model cannot be empty") + } + if contextWindow <= 0 { + return E.New("contextWindow must be positive") + } + + u.mutex.Lock() + defer u.mutex.Unlock() + + u.LastUpdated = time.Now() + + // Find or create combination + var combo *CostCombination + for i := range u.Combinations { + if u.Combinations[i].Model == model && u.Combinations[i].ContextWindow == contextWindow { + combo = &u.Combinations[i] + break + } + } + + if combo == nil { + newCombo := CostCombination{ + Model: model, + ContextWindow: contextWindow, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), + } + u.Combinations = append(u.Combinations, newCombo) + combo = &u.Combinations[len(u.Combinations)-1] + } + + // Update total stats + combo.Total.RequestCount++ + combo.Total.MessagesCount += messagesCount + combo.Total.InputTokens += inputTokens + combo.Total.OutputTokens += outputTokens + combo.Total.CacheReadInputTokens += cacheReadTokens + combo.Total.CacheCreationInputTokens += cacheCreationTokens + + // Update per-user stats if user is specified + if user != "" { + userStats := combo.ByUser[user] + userStats.RequestCount++ + userStats.MessagesCount += messagesCount + userStats.InputTokens += inputTokens + userStats.OutputTokens += outputTokens + userStats.CacheReadInputTokens += cacheReadTokens + userStats.CacheCreationInputTokens += cacheCreationTokens + combo.ByUser[user] = userStats + } + + go u.scheduleSave() + + return nil +} + +func (u *AggregatedUsage) scheduleSave() { + const saveInterval = time.Minute + + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + timeSinceLastSave := time.Since(u.lastSaveTime) + + if timeSinceLastSave >= saveInterval { + go u.saveAsync() + return + } + + if u.pendingSave { + return + } + + u.pendingSave = true + remainingTime := saveInterval - timeSinceLastSave + + u.saveTimer = time.AfterFunc(remainingTime, func() { + u.saveMutex.Lock() + u.pendingSave = false + u.saveMutex.Unlock() + u.saveAsync() + }) +} + +func (u *AggregatedUsage) saveAsync() { + err := u.Save() + if err != nil { + if u.logger != nil { + u.logger.Error("save usage statistics: ", err) + } + } +} + +func (u *AggregatedUsage) cancelPendingSave() { + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + if u.saveTimer != nil { + u.saveTimer.Stop() + u.saveTimer = nil + } + u.pendingSave = false +} diff --git a/service/ccm/service_user.go b/service/ccm/service_user.go new file mode 100644 index 00000000..94637ed8 --- /dev/null +++ b/service/ccm/service_user.go @@ -0,0 +1,29 @@ +package ccm + +import ( + "sync" + + "github.com/sagernet/sing-box/option" +) + +type UserManager struct { + accessMutex sync.RWMutex + tokenMap map[string]string +} + +func (m *UserManager) UpdateUsers(users []option.CCMUser) { + m.accessMutex.Lock() + defer m.accessMutex.Unlock() + tokenMap := make(map[string]string, len(users)) + for _, user := range users { + tokenMap[user.Token] = user.Name + } + m.tokenMap = tokenMap +} + +func (m *UserManager) Authenticate(token string) (string, bool) { + m.accessMutex.RLock() + username, found := m.tokenMap[token] + m.accessMutex.RUnlock() + return username, found +} From 1c4614318e2a7673844283cf1f685487c0a152cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 21 Oct 2025 21:21:49 +0800 Subject: [PATCH 039/185] Fix read credentials for ccm service --- docs/configuration/service/ccm.md | 4 +++- docs/configuration/service/ccm.zh.md | 4 +++- service/ccm/credential.go | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/configuration/service/ccm.md b/docs/configuration/service/ccm.md index e0ccfa1f..28b82710 100644 --- a/docs/configuration/service/ccm.md +++ b/docs/configuration/service/ccm.md @@ -37,7 +37,9 @@ See [Listen Fields](/configuration/shared/listen/) for details. Path to the Claude Code OAuth credentials file. -Defaults to `~/.claude/.credentials.json` if not specified. +If not specified, defaults to: +- `$CLAUDE_CONFIG_DIR/.credentials.json` if `CLAUDE_CONFIG_DIR` environment variable is set +- `~/.claude/.credentials.json` otherwise On macOS, credentials are read from the system keychain first, then fall back to the file if unavailable. diff --git a/docs/configuration/service/ccm.zh.md b/docs/configuration/service/ccm.zh.md index fa5e5d51..cd5d3471 100644 --- a/docs/configuration/service/ccm.zh.md +++ b/docs/configuration/service/ccm.zh.md @@ -37,7 +37,9 @@ CCM(Claude Code 多路复用器)服务是一个多路复用服务,允许 Claude Code OAuth 凭据文件的路径。 -如果未指定,默认使用 `~/.claude/.credentials.json`。 +如果未指定,默认值为: +- 如果设置了 `CLAUDE_CONFIG_DIR` 环境变量,则使用 `$CLAUDE_CONFIG_DIR/.credentials.json` +- 否则使用 `~/.claude/.credentials.json` 在 macOS 上,首先从系统钥匙串读取凭据,如果不可用则回退到文件。 diff --git a/service/ccm/credential.go b/service/ccm/credential.go index 5fd79721..695efc7a 100644 --- a/service/ccm/credential.go +++ b/service/ccm/credential.go @@ -32,6 +32,9 @@ func getRealUser() (*user.User, error) { } func getDefaultCredentialsPath() (string, error) { + if configDir := os.Getenv("CLAUDE_CONFIG_DIR"); configDir != "" { + return filepath.Join(configDir, ".credentials.json"), nil + } userInfo, err := getRealUser() if err != nil { return "", err From e92938364dadbd9e90df8357f5618f0b5f86ee01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 3 Dec 2025 11:04:27 +0800 Subject: [PATCH 040/185] Update quic-go to v0.57.1 --- go.mod | 6 +++--- go.sum | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index c34abc0e..4145299d 100644 --- a/go.mod +++ b/go.mod @@ -28,10 +28,10 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 - github.com/sagernet/quic-go v0.55.0-sing-box-mod.2 + github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 github.com/sagernet/sing v0.8.0-beta.6 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.4 + github.com/sagernet/sing-quic v0.6.0-beta.5 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 @@ -102,7 +102,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect - github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect diff --git a/go.sum b/go.sum index ebeb026e..d6370a54 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,6 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= -github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= @@ -140,8 +138,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= @@ -161,14 +159,14 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.55.0-sing-box-mod.2 h1:I79gW4Xl5ciVARHfnp122lDAMhC0AwUCU765Q8Kxdfo= -github.com/sagernet/quic-go v0.55.0-sing-box-mod.2/go.mod h1:IE9naq7Kekj0rPAdWc0GLW1ENR7gAOQV9VRTDlKN8Bk= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= github.com/sagernet/sing v0.8.0-beta.6 h1:GXv1j1xWHihx6ptyOXh0yp4jUqJoNjCqD8d+AI9rnLU= github.com/sagernet/sing v0.8.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.4 h1:2k/+Xrv/pjl7AYC7LD9tcB7y1lIgw04LjJjqTI8q5Xk= -github.com/sagernet/sing-quic v0.6.0-beta.4/go.mod h1:FNvKPADzMZprwm7UQCcCGPhYifpb5rxoCOntOupJU+8= +github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4= +github.com/sagernet/sing-quic v0.6.0-beta.5/go.mod h1:9D9GANrK33NjWCe1VkU5L5+8MxU39WrduBSmHuHz8GA= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= From cd56eaaba219ce5bdb04cdac695cd8d62ed29a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 6 Dec 2025 11:23:01 +0800 Subject: [PATCH 041/185] Add more tcp keep alive options Also update default TCP keep-alive initial period from 10 minutes to 5 minutes. --- common/dialer/default.go | 15 ++++++++-- common/listener/listener_tcp.go | 2 +- constant/timeout.go | 2 +- docs/configuration/shared/dial.md | 35 ++++++++++++++++++++++- docs/configuration/shared/dial.zh.md | 34 ++++++++++++++++++++++ docs/configuration/shared/listen.md | 30 ++++++++++++++++++++ docs/configuration/shared/listen.zh.md | 30 ++++++++++++++++++++ option/inbound.go | 1 + option/outbound.go | 39 ++++++++++++++------------ 9 files changed, 164 insertions(+), 24 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index 06f637a3..23754e0a 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -143,9 +143,18 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial } else { dialer.Timeout = C.TCPConnectTimeout } - // TODO: Add an option to customize the keep alive period - dialer.KeepAlive = C.TCPKeepAliveInitial - dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval)) + if !options.DisableTCPKeepAlive { + keepIdle := time.Duration(options.TCPKeepAlive) + if keepIdle == 0 { + keepIdle = C.TCPKeepAliveInitial + } + keepInterval := time.Duration(options.TCPKeepAliveInterval) + if keepInterval == 0 { + keepInterval = C.TCPKeepAliveInterval + } + dialer.KeepAlive = keepIdle + dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval)) + } var udpFragment bool if options.UDPFragment != nil { udpFragment = *options.UDPFragment diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index e63d643c..2164ff8e 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -37,7 +37,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) { if l.listenOptions.ReuseAddr { listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) } - if l.listenOptions.TCPKeepAlive >= 0 { + if !l.listenOptions.DisableTCPKeepAlive { keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) if keepIdle == 0 { keepIdle = C.TCPKeepAliveInitial diff --git a/constant/timeout.go b/constant/timeout.go index eb0fd34c..e1bc7ccd 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -3,7 +3,7 @@ package constant import "time" const ( - TCPKeepAliveInitial = 10 * time.Minute + TCPKeepAliveInitial = 5 * time.Minute TCPKeepAliveInterval = 75 * time.Second TCPConnectTimeout = 5 * time.Second TCPTimeout = 15 * time.Second diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index f48f355d..cf775b65 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -2,6 +2,12 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [tcp_keep_alive](#tcp_keep_alive) + :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [domain_resolver](#domain_resolver) @@ -29,8 +35,11 @@ icon: material/new-box "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, - + "domain_resolver": "", // or {} "network_strategy": "", "network_type": [], @@ -112,6 +121,30 @@ Enable TCP Fast Open. Enable TCP Multi Path. +#### disable_tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + +Disable TCP keep alive. + +#### tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + + Default value changed from `10m` to `5m`. + +TCP keep-alive initial period. + +`5m` will be used by default. + +#### tcp_keep_alive_interval + +!!! question "Since sing-box 1.13.0" + +TCP keep-alive interval. + +`75s` will be used by default. + #### udp_fragment Enable UDP fragmentation. diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index babb43e9..acd62213 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -2,6 +2,12 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [tcp_keep_alive](#tcp_keep_alive) + :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + !!! quote "sing-box 1.12.0 中的更改" :material-plus: [domain_resolver](#domain_resolver) @@ -29,7 +35,11 @@ icon: material/new-box "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, + "domain_resolver": "", // 或 {} "network_strategy": "", "network_type": [], @@ -109,6 +119,30 @@ icon: material/new-box 启用 TCP Multi Path。 +#### disable_tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + +禁用 TCP keep alive。 + +#### tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + + 默认值从 `10m` 更改为 `5m`。 + +TCP keep-alive 初始周期。 + +默认使用 `5m`。 + +#### tcp_keep_alive_interval + +!!! question "自 sing-box 1.13.0 起" + +TCP keep-alive 间隔。 + +默认使用 `75s`。 + #### udp_fragment 启用 UDP 分段。 diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index 4040e42f..38a3749c 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-alert: [tcp_keep_alive](#tcp_keep_alive) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [netns](#netns) @@ -29,6 +34,9 @@ icon: material/new-box "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, "udp_timeout": "", "detour": "", @@ -101,6 +109,28 @@ Enable TCP Fast Open. Enable TCP Multi Path. +#### disable_tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + +Disable TCP keep alive. + +#### tcp_keep_alive + +!!! question "Since sing-box 1.13.0" + + Default value changed from `10m` to `5m`. + +TCP keep alive initial period. + +`5m` will be used by default. + +#### tcp_keep_alive_interval + +TCP keep-alive interval. + +`75s` will be used by default. + #### udp_fragment Enable UDP fragmentation. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index cd12036c..8893f37b 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-alert: [tcp_keep_alive](#tcp_keep_alive) + !!! quote "Changes in sing-box 1.12.0" :material-plus: [netns](#netns) @@ -29,6 +34,9 @@ icon: material/new-box "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, + "disable_tcp_keep_alive": false, + "tcp_keep_alive": "", + "tcp_keep_alive_interval": "", "udp_fragment": false, "udp_timeout": "", "detour": "", @@ -101,6 +109,28 @@ icon: material/new-box 启用 TCP Multi Path。 +#### disable_tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + +禁用 TCP keep alive。 + +#### tcp_keep_alive + +!!! question "自 sing-box 1.13.0 起" + + 默认值从 `10m` 更改为 `5m`。 + +TCP keep alive 初始周期。 + +默认使用 `5m`。 + +#### tcp_keep_alive_interval + +TCP keep-alive 间隔。 + +默认使用 `75s`。 + #### udp_fragment 启用 UDP 分段。 diff --git a/option/inbound.go b/option/inbound.go index 64ded9b1..42930ecc 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -65,6 +65,7 @@ type ListenOptions struct { RoutingMark FwMark `json:"routing_mark,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"` NetNs string `json:"netns,omitempty"` + DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"` TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"` TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index 2520d000..b5c52fa2 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -65,24 +65,27 @@ type DialerOptionsWrapper interface { } type DialerOptions struct { - Detour string `json:"detour,omitempty"` - BindInterface string `json:"bind_interface,omitempty"` - Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` - Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` - ProtectPath string `json:"protect_path,omitempty"` - RoutingMark FwMark `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,omitempty"` - NetNs string `json:"netns,omitempty"` - ConnectTimeout badoption.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:"-"` - DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` - NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` - NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` - FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` - FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` + Detour string `json:"detour,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` + Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` + ProtectPath string `json:"protect_path,omitempty"` + RoutingMark FwMark `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + NetNs string `json:"netns,omitempty"` + ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,omitempty"` + TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"` + TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` + NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` + FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` // Deprecated: migrated to domain resolver DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` From 8d8ca282a148f5977190d1bd59d482b369adedde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 7 Dec 2025 11:05:43 +0800 Subject: [PATCH 042/185] Add Linux WI-FI state support Support monitoring WIFI state on Linux through: - NetworkManager (D-Bus) - IWD (D-Bus) - wpa_supplicant (control socket) - ConnMan (D-Bus) --- adapter/network.go | 4 +- adapter/router.go | 1 - box.go | 2 +- common/settings/wifi.go | 9 + common/settings/wifi_linux.go | 46 +++++ common/settings/wifi_linux_connman.go | 166 ++++++++++++++++ common/settings/wifi_linux_iwd.go | 188 ++++++++++++++++++ common/settings/wifi_linux_nm.go | 163 ++++++++++++++++ common/settings/wifi_linux_wpa.go | 225 ++++++++++++++++++++++ common/settings/wifi_stub.go | 27 +++ docs/configuration/dns/rule.md | 4 +- docs/configuration/dns/rule.zh.md | 4 +- docs/configuration/route/rule.md | 4 +- docs/configuration/route/rule.zh.md | 4 +- experimental/libbox/config.go | 4 + experimental/libbox/platform/interface.go | 1 + experimental/libbox/service.go | 6 +- route/network.go | 76 +++++++- route/router.go | 10 +- 19 files changed, 913 insertions(+), 31 deletions(-) create mode 100644 common/settings/wifi.go create mode 100644 common/settings/wifi_linux.go create mode 100644 common/settings/wifi_linux_connman.go create mode 100644 common/settings/wifi_linux_iwd.go create mode 100644 common/settings/wifi_linux_nm.go create mode 100644 common/settings/wifi_linux_wpa.go create mode 100644 common/settings/wifi_stub.go diff --git a/adapter/network.go b/adapter/network.go index 1b26bed6..dd53b2b4 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -10,6 +10,7 @@ import ( type NetworkManager interface { Lifecycle + Initialize(ruleSets []RuleSet) InterfaceFinder() control.InterfaceFinder UpdateInterfaces() error DefaultNetworkInterface() *NetworkInterface @@ -24,9 +25,10 @@ type NetworkManager interface { NetworkMonitor() tun.NetworkUpdateMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor PackageManager() tun.PackageManager + NeedWIFIState() bool WIFIState() WIFIState - ResetNetwork() UpdateWIFIState() + ResetNetwork() } type NetworkOptions struct { diff --git a/adapter/router.go b/adapter/router.go index 522a0d9d..3cece2ee 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -24,7 +24,6 @@ type Router interface { PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) ConnectionRouterEx RuleSet(tag string) (RuleSet, bool) - NeedWIFIState() bool Rules() []Rule AppendTracker(tracker ConnectionTracker) ResetNetwork() diff --git a/box.go b/box.go index d43a3c95..a84437ef 100644 --- a/box.go +++ b/box.go @@ -184,7 +184,7 @@ func New(options Options) (*Box, error) { service.MustRegister[adapter.ServiceManager](ctx, serviceManager) dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions) service.MustRegister[adapter.DNSRouter](ctx, dnsRouter) - networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions) + networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions) if err != nil { return nil, E.Cause(err, "initialize network manager") } diff --git a/common/settings/wifi.go b/common/settings/wifi.go new file mode 100644 index 00000000..62bef706 --- /dev/null +++ b/common/settings/wifi.go @@ -0,0 +1,9 @@ +package settings + +import "github.com/sagernet/sing-box/adapter" + +type WIFIMonitor interface { + ReadWIFIState() adapter.WIFIState + Start() error + Close() error +} diff --git a/common/settings/wifi_linux.go b/common/settings/wifi_linux.go new file mode 100644 index 00000000..9deed3c8 --- /dev/null +++ b/common/settings/wifi_linux.go @@ -0,0 +1,46 @@ +package settings + +import ( + "github.com/sagernet/sing-box/adapter" + E "github.com/sagernet/sing/common/exceptions" +) + +type LinuxWIFIMonitor struct { + monitor WIFIMonitor +} + +func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + monitors := []func(func(adapter.WIFIState)) (WIFIMonitor, error){ + newNetworkManagerMonitor, + newIWDMonitor, + newWpaSupplicantMonitor, + newConnManMonitor, + } + var errors []error + for _, factory := range monitors { + monitor, err := factory(callback) + if err == nil { + return &LinuxWIFIMonitor{monitor: monitor}, nil + } + errors = append(errors, err) + } + return nil, E.Cause(E.Errors(errors...), "no supported WIFI manager found") +} + +func (m *LinuxWIFIMonitor) ReadWIFIState() adapter.WIFIState { + return m.monitor.ReadWIFIState() +} + +func (m *LinuxWIFIMonitor) Start() error { + if m.monitor != nil { + return m.monitor.Start() + } + return nil +} + +func (m *LinuxWIFIMonitor) Close() error { + if m.monitor != nil { + return m.monitor.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_connman.go b/common/settings/wifi_linux_connman.go new file mode 100644 index 00000000..130636bc --- /dev/null +++ b/common/settings/wifi_linux_connman.go @@ -0,0 +1,166 @@ +package settings + +import ( + "context" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + + "github.com/godbus/dbus/v5" +) + +type connmanMonitor struct { + conn *dbus.Conn + callback func(adapter.WIFIState) + cancel context.CancelFunc + signalChan chan *dbus.Signal +} + +func newConnManMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + cmObj := conn.Object("net.connman", "/") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + call := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0) + if call.Err != nil { + conn.Close() + return nil, call.Err + } + return &connmanMonitor{conn: conn, callback: callback}, nil +} + +func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + cmObj := m.conn.Object("net.connman", "/") + var services []interface{} + err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services) + if err != nil { + return adapter.WIFIState{} + } + + for _, service := range services { + servicePair, ok := service.([]interface{}) + if !ok || len(servicePair) != 2 { + continue + } + + serviceProps, ok := servicePair[1].(map[string]dbus.Variant) + if !ok { + continue + } + + typeVariant, hasType := serviceProps["Type"] + if !hasType { + continue + } + serviceType, ok := typeVariant.Value().(string) + if !ok || serviceType != "wifi" { + continue + } + + stateVariant, hasState := serviceProps["State"] + if !hasState { + continue + } + state, ok := stateVariant.Value().(string) + if !ok || (state != "online" && state != "ready") { + continue + } + + nameVariant, hasName := serviceProps["Name"] + if !hasName { + continue + } + ssid, ok := nameVariant.Value().(string) + if !ok || ssid == "" { + continue + } + + bssidVariant, hasBSSID := serviceProps["BSSID"] + if !hasBSSID { + return adapter.WIFIState{SSID: ssid} + } + bssid, ok := bssidVariant.Value().(string) + if !ok { + return adapter.WIFIState{SSID: ssid} + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")), + } + } + + return adapter.WIFIState{} +} + +func (m *connmanMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.signalChan = make(chan *dbus.Signal, 10) + m.conn.Signal(m.signalChan) + + err := m.conn.AddMatchSignal( + dbus.WithMatchInterface("net.connman.Service"), + dbus.WithMatchSender("net.connman"), + ) + if err != nil { + return err + } + + state := m.ReadWIFIState() + go m.monitorSignals(ctx, m.signalChan, state) + m.callback(state) + + return nil +} + +func (m *connmanMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-signalChan: + if !ok { + return + } + // godbus Signal.Name uses "interface.member" format (e.g. "net.connman.Service.PropertyChanged"), + // not just the member name. This differs from the D-Bus signal member in the match rule. + if signal.Name == "net.connman.Service.PropertyChanged" { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + } + } + } +} + +func (m *connmanMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + if m.signalChan != nil { + m.conn.RemoveSignal(m.signalChan) + close(m.signalChan) + } + if m.conn != nil { + m.conn.RemoveMatchSignal( + dbus.WithMatchInterface("net.connman.Service"), + dbus.WithMatchSender("net.connman"), + ) + return m.conn.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_iwd.go b/common/settings/wifi_linux_iwd.go new file mode 100644 index 00000000..22dfe720 --- /dev/null +++ b/common/settings/wifi_linux_iwd.go @@ -0,0 +1,188 @@ +package settings + +import ( + "context" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + + "github.com/godbus/dbus/v5" +) + +type iwdMonitor struct { + conn *dbus.Conn + callback func(adapter.WIFIState) + cancel context.CancelFunc + signalChan chan *dbus.Signal +} + +func newIWDMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + iwdObj := conn.Object("net.connman.iwd", "/") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + call := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0) + if call.Err != nil { + conn.Close() + return nil, call.Err + } + return &iwdMonitor{conn: conn, callback: callback}, nil +} + +func (m *iwdMonitor) ReadWIFIState() adapter.WIFIState { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + iwdObj := m.conn.Object("net.connman.iwd", "/") + var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant + err := iwdObj.CallWithContext(ctx, "org.freedesktop.DBus.ObjectManager.GetManagedObjects", 0).Store(&objects) + if err != nil { + return adapter.WIFIState{} + } + + for _, interfaces := range objects { + stationProps, hasStation := interfaces["net.connman.iwd.Station"] + if !hasStation { + continue + } + + stateVariant, hasState := stationProps["State"] + if !hasState { + continue + } + state, ok := stateVariant.Value().(string) + if !ok || state != "connected" { + continue + } + + connectedNetworkVariant, hasNetwork := stationProps["ConnectedNetwork"] + if !hasNetwork { + continue + } + networkPath, ok := connectedNetworkVariant.Value().(dbus.ObjectPath) + if !ok || networkPath == "/" { + continue + } + + networkInterfaces, hasNetworkPath := objects[networkPath] + if !hasNetworkPath { + continue + } + + networkProps, hasNetworkInterface := networkInterfaces["net.connman.iwd.Network"] + if !hasNetworkInterface { + continue + } + + nameVariant, hasName := networkProps["Name"] + if !hasName { + continue + } + ssid, ok := nameVariant.Value().(string) + if !ok { + continue + } + + connectedBSSVariant, hasBSS := stationProps["ConnectedAccessPoint"] + if !hasBSS { + return adapter.WIFIState{SSID: ssid} + } + bssPath, ok := connectedBSSVariant.Value().(dbus.ObjectPath) + if !ok || bssPath == "/" { + return adapter.WIFIState{SSID: ssid} + } + + bssInterfaces, hasBSSPath := objects[bssPath] + if !hasBSSPath { + return adapter.WIFIState{SSID: ssid} + } + + bssProps, hasBSSInterface := bssInterfaces["net.connman.iwd.BasicServiceSet"] + if !hasBSSInterface { + return adapter.WIFIState{SSID: ssid} + } + + addressVariant, hasAddress := bssProps["Address"] + if !hasAddress { + return adapter.WIFIState{SSID: ssid} + } + bssid, ok := addressVariant.Value().(string) + if !ok { + return adapter.WIFIState{SSID: ssid} + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")), + } + } + + return adapter.WIFIState{} +} + +func (m *iwdMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.signalChan = make(chan *dbus.Signal, 10) + m.conn.Signal(m.signalChan) + + err := m.conn.AddMatchSignal( + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + dbus.WithMatchSender("net.connman.iwd"), + ) + if err != nil { + return err + } + + state := m.ReadWIFIState() + go m.monitorSignals(ctx, m.signalChan, state) + m.callback(state) + + return nil +} + +func (m *iwdMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-signalChan: + if !ok { + return + } + if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + } + } + } +} + +func (m *iwdMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + if m.signalChan != nil { + m.conn.RemoveSignal(m.signalChan) + close(m.signalChan) + } + if m.conn != nil { + m.conn.RemoveMatchSignal( + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + dbus.WithMatchSender("net.connman.iwd"), + ) + return m.conn.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_nm.go b/common/settings/wifi_linux_nm.go new file mode 100644 index 00000000..4288d210 --- /dev/null +++ b/common/settings/wifi_linux_nm.go @@ -0,0 +1,163 @@ +package settings + +import ( + "context" + "strings" + "time" + + "github.com/sagernet/sing-box/adapter" + + "github.com/godbus/dbus/v5" +) + +type networkManagerMonitor struct { + conn *dbus.Conn + callback func(adapter.WIFIState) + cancel context.CancelFunc + signalChan chan *dbus.Signal +} + +func newNetworkManagerMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + nmObj := conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + var state uint32 + err = nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "State").Store(&state) + if err != nil { + conn.Close() + return nil, err + } + return &networkManagerMonitor{conn: conn, callback: callback}, nil +} + +func (m *networkManagerMonitor) ReadWIFIState() adapter.WIFIState { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + nmObj := m.conn.Object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager") + + var activeConnectionPaths []dbus.ObjectPath + err := nmObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager", "ActiveConnections").Store(&activeConnectionPaths) + if err != nil || len(activeConnectionPaths) == 0 { + return adapter.WIFIState{} + } + + for _, connectionPath := range activeConnectionPaths { + connObj := m.conn.Object("org.freedesktop.NetworkManager", connectionPath) + + var devicePaths []dbus.ObjectPath + err = connObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "Devices").Store(&devicePaths) + if err != nil || len(devicePaths) == 0 { + continue + } + + for _, devicePath := range devicePaths { + deviceObj := m.conn.Object("org.freedesktop.NetworkManager", devicePath) + + var deviceType uint32 + err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device", "DeviceType").Store(&deviceType) + if err != nil || deviceType != 2 { + continue + } + + var accessPointPath dbus.ObjectPath + err = deviceObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint").Store(&accessPointPath) + if err != nil || accessPointPath == "/" { + continue + } + + apObj := m.conn.Object("org.freedesktop.NetworkManager", accessPointPath) + + var ssidBytes []byte + err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "Ssid").Store(&ssidBytes) + if err != nil { + continue + } + + var hwAddress string + err = apObj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.AccessPoint", "HwAddress").Store(&hwAddress) + if err != nil { + continue + } + + ssid := strings.TrimSpace(string(ssidBytes)) + if ssid == "" { + continue + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(hwAddress, ":", "")), + } + } + } + + return adapter.WIFIState{} +} + +func (m *networkManagerMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.signalChan = make(chan *dbus.Signal, 10) + m.conn.Signal(m.signalChan) + + err := m.conn.AddMatchSignal( + dbus.WithMatchSender("org.freedesktop.NetworkManager"), + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + ) + if err != nil { + return err + } + + state := m.ReadWIFIState() + go m.monitorSignals(ctx, m.signalChan, state) + m.callback(state) + + return nil +} + +func (m *networkManagerMonitor) monitorSignals(ctx context.Context, signalChan chan *dbus.Signal, lastState adapter.WIFIState) { + for { + select { + case <-ctx.Done(): + return + case signal, ok := <-signalChan: + if !ok { + return + } + if signal.Name == "org.freedesktop.DBus.Properties.PropertiesChanged" { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + } + } + } +} + +func (m *networkManagerMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + if m.signalChan != nil { + m.conn.RemoveSignal(m.signalChan) + close(m.signalChan) + } + if m.conn != nil { + m.conn.RemoveMatchSignal( + dbus.WithMatchSender("org.freedesktop.NetworkManager"), + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + ) + return m.conn.Close() + } + return nil +} diff --git a/common/settings/wifi_linux_wpa.go b/common/settings/wifi_linux_wpa.go new file mode 100644 index 00000000..51e76c1c --- /dev/null +++ b/common/settings/wifi_linux_wpa.go @@ -0,0 +1,225 @@ +package settings + +import ( + "bufio" + "context" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/sagernet/sing-box/adapter" +) + +var wpaSocketCounter atomic.Uint64 + +type wpaSupplicantMonitor struct { + socketPath string + callback func(adapter.WIFIState) + cancel context.CancelFunc + monitorConn *net.UnixConn + connMutex sync.Mutex +} + +func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + socketDirs := []string{"/var/run/wpa_supplicant", "/run/wpa_supplicant"} + for _, socketDir := range socketDirs { + entries, err := os.ReadDir(socketDir) + if err != nil { + continue + } + for _, entry := range entries { + if entry.IsDir() || entry.Name() == "." || entry.Name() == ".." { + continue + } + socketPath := filepath.Join(socketDir, entry.Name()) + id := wpaSocketCounter.Add(1) + localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"} + remoteAddr := &net.UnixAddr{Name: socketPath, Net: "unixgram"} + conn, err := net.DialUnix("unixgram", localAddr, remoteAddr) + if err != nil { + continue + } + conn.Close() + return &wpaSupplicantMonitor{socketPath: socketPath, callback: callback}, nil + } + } + return nil, os.ErrNotExist +} + +func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState { + id := wpaSocketCounter.Add(1) + localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d-%d", os.Getpid(), id), Net: "unixgram"} + remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"} + conn, err := net.DialUnix("unixgram", localAddr, remoteAddr) + if err != nil { + return adapter.WIFIState{} + } + defer conn.Close() + + conn.SetDeadline(time.Now().Add(3 * time.Second)) + + status, err := m.sendCommand(conn, "STATUS") + if err != nil { + return adapter.WIFIState{} + } + + var ssid, bssid string + var connected bool + scanner := bufio.NewScanner(strings.NewReader(status)) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "wpa_state=") { + state := strings.TrimPrefix(line, "wpa_state=") + connected = state == "COMPLETED" + } else if strings.HasPrefix(line, "ssid=") { + ssid = strings.TrimPrefix(line, "ssid=") + } else if strings.HasPrefix(line, "bssid=") { + bssid = strings.TrimPrefix(line, "bssid=") + } + } + + if !connected || ssid == "" { + return adapter.WIFIState{} + } + + return adapter.WIFIState{ + SSID: ssid, + BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")), + } +} + +// sendCommand sends a command to wpa_supplicant and returns the response. +// Commands are sent without trailing newlines per the wpa_supplicant control +// interface protocol - the official wpa_ctrl.c sends raw command strings. +func (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) { + _, err := conn.Write([]byte(command)) + if err != nil { + return "", err + } + + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil { + return "", err + } + + response := string(buf[:n]) + if strings.HasPrefix(response, "FAIL") { + return "", os.ErrInvalid + } + + return strings.TrimSpace(response), nil +} + +func (m *wpaSupplicantMonitor) Start() error { + if m.callback == nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + state := m.ReadWIFIState() + go m.monitorEvents(ctx, state) + m.callback(state) + + return nil +} + +func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) { + var consecutiveErrors int + var debounceTimer *time.Timer + var debounceMutex sync.Mutex + + localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-mon-%d", os.Getpid()), Net: "unixgram"} + remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"} + conn, err := net.DialUnix("unixgram", localAddr, remoteAddr) + if err != nil { + return + } + defer conn.Close() + + m.connMutex.Lock() + m.monitorConn = conn + m.connMutex.Unlock() + + // ATTACH/DETACH commands use os_strcmp() for exact matching in wpa_supplicant, + // so they must be sent without trailing newlines. + // See: https://w1.fi/cgit/hostap/tree/wpa_supplicant/ctrl_iface_unix.c + _, err = conn.Write([]byte("ATTACH")) + if err != nil { + return + } + + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil || !strings.HasPrefix(string(buf[:n]), "OK") { + return + } + + for { + select { + case <-ctx.Done(): + debounceMutex.Lock() + if debounceTimer != nil { + debounceTimer.Stop() + } + debounceMutex.Unlock() + conn.Write([]byte("DETACH")) + return + default: + } + + conn.SetReadDeadline(time.Now().Add(30 * time.Second)) + n, err := conn.Read(buf) + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + continue + } + select { + case <-ctx.Done(): + return + default: + } + consecutiveErrors++ + if consecutiveErrors > 10 { + return + } + time.Sleep(time.Second) + continue + } + consecutiveErrors = 0 + + msg := string(buf[:n]) + if strings.Contains(msg, "CTRL-EVENT-CONNECTED") || strings.Contains(msg, "CTRL-EVENT-DISCONNECTED") { + debounceMutex.Lock() + if debounceTimer != nil { + debounceTimer.Stop() + } + debounceTimer = time.AfterFunc(500*time.Millisecond, func() { + state := m.ReadWIFIState() + if state != lastState { + lastState = state + m.callback(state) + } + }) + debounceMutex.Unlock() + } + } +} + +func (m *wpaSupplicantMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + m.connMutex.Lock() + if m.monitorConn != nil { + m.monitorConn.Close() + } + m.connMutex.Unlock() + return nil +} diff --git a/common/settings/wifi_stub.go b/common/settings/wifi_stub.go new file mode 100644 index 00000000..98db500f --- /dev/null +++ b/common/settings/wifi_stub.go @@ -0,0 +1,27 @@ +//go:build !linux + +package settings + +import ( + "os" + + "github.com/sagernet/sing-box/adapter" +) + +type stubWIFIMonitor struct{} + +func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + return nil, os.ErrInvalid +} + +func (m *stubWIFIMonitor) ReadWIFIState() adapter.WIFIState { + return adapter.WIFIState{} +} + +func (m *stubWIFIMonitor) Start() error { + return nil +} + +func (m *stubWIFIMonitor) Close() error { + return nil +} diff --git a/docs/configuration/dns/rule.md b/docs/configuration/dns/rule.md index 524b70a2..6407e1bf 100644 --- a/docs/configuration/dns/rule.md +++ b/docs/configuration/dns/rule.md @@ -412,7 +412,7 @@ Match default interface address. !!! quote "" - Only supported in graphical clients on Android and Apple platforms. + Only supported in graphical clients on Android and Apple platforms, or on Linux. Match WiFi SSID. @@ -420,7 +420,7 @@ Match WiFi SSID. !!! quote "" - Only supported in graphical clients on Android and Apple platforms. + Only supported in graphical clients on Android and Apple platforms, or on Linux. Match WiFi BSSID. diff --git a/docs/configuration/dns/rule.zh.md b/docs/configuration/dns/rule.zh.md index bc812c77..588e0736 100644 --- a/docs/configuration/dns/rule.zh.md +++ b/docs/configuration/dns/rule.zh.md @@ -411,7 +411,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. !!! quote "" - 仅在 Android 与 Apple 平台图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 匹配 WiFi SSID。 @@ -419,7 +419,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`. !!! quote "" - 仅在 Android 与 Apple 平台图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 匹配 WiFi BSSID。 diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index a6e89d7b..6bbb00de 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -430,7 +430,7 @@ Match default interface address. !!! quote "" - Only supported in graphical clients on Android and Apple platforms. + Only supported in graphical clients on Android and Apple platforms, or on Linux. Match WiFi SSID. @@ -438,7 +438,7 @@ Match WiFi SSID. !!! quote "" - Only supported in graphical clients on Android and Apple platforms. + Only supported in graphical clients on Android and Apple platforms, or on Linux. Match WiFi BSSID. diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index a90607d4..164bf40e 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -427,7 +427,7 @@ icon: material/new-box !!! quote "" - 仅在 Android 与 Apple 平台图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 匹配 WiFi SSID。 @@ -435,7 +435,7 @@ icon: material/new-box !!! quote "" - 仅在 Android 与 Apple 平台图形客户端中支持。 + 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 匹配 WiFi BSSID。 diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 89c51222..5df7d141 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -107,6 +107,10 @@ func (s *platformInterfaceStub) IncludeAllNetworks() bool { func (s *platformInterfaceStub) ClearDNSCache() { } +func (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool { + return false +} + func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { return adapter.WIFIState{} } diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 35b0830b..d5b6d3c7 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -18,6 +18,7 @@ type Interface interface { UnderNetworkExtension() bool IncludeAllNetworks() bool ClearDNSCache() + UsePlatformWIFIMonitor() bool ReadWIFIState() adapter.WIFIState SystemCertificates() []string process.Searcher diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 8c94b389..c272825f 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -111,7 +111,7 @@ func (s *BoxService) Close() error { } func (s *BoxService) NeedWIFIState() bool { - return s.instance.Router().NeedWIFIState() + return s.instance.Network().NeedWIFIState() } var ( @@ -224,6 +224,10 @@ func (w *platformInterfaceWrapper) ClearDNSCache() { w.iif.ClearDNSCache() } +func (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool { + return true +} + func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { wifiState := w.iif.ReadWIFIState() if wifiState == nil { diff --git a/route/network.go b/route/network.go index 5009ec0b..52d74c8c 100644 --- a/route/network.go +++ b/route/network.go @@ -8,11 +8,13 @@ import ( "os" "runtime" "strings" + "sync" "syscall" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/conntrack" + "github.com/sagernet/sing-box/common/settings" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/libbox/platform" @@ -50,11 +52,14 @@ type NetworkManager struct { endpoint adapter.EndpointManager inbound adapter.InboundManager outbound adapter.OutboundManager + needWIFIState bool + wifiMonitor settings.WIFIMonitor wifiState adapter.WIFIState + wifiStateMutex sync.RWMutex started bool } -func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { +func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) { defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DefaultDomainResolver) if routeOptions.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) { return nil, E.New("`auto_detect_interface` is only supported on Linux, Windows and macOS") @@ -89,6 +94,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp endpoint: service.FromContext[adapter.EndpointManager](ctx), inbound: service.FromContext[adapter.InboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx), + needWIFIState: hasRule(routeOptions.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } if routeOptions.DefaultNetworkStrategy != nil { if routeOptions.DefaultInterface != "" { @@ -183,11 +189,35 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { } } case adapter.StartStatePostStart: + if r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) { + wifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged) + if err != nil { + if err != os.ErrInvalid { + r.logger.Warn(E.Cause(err, "create WIFI monitor")) + } + } else { + r.wifiMonitor = wifiMonitor + err = r.wifiMonitor.Start() + if err != nil { + r.logger.Warn(E.Cause(err, "start WIFI monitor")) + } + } + } r.started = true } return nil } +func (r *NetworkManager) Initialize(ruleSets []adapter.RuleSet) { + for _, ruleSet := range ruleSets { + metadata := ruleSet.Metadata() + if metadata.ContainsWIFIRule { + r.needWIFIState = true + break + } + } +} + func (r *NetworkManager) Close() error { monitor := taskmonitor.New(r.logger, C.StopTimeout) var err error @@ -219,6 +249,13 @@ func (r *NetworkManager) Close() error { }) monitor.Finish() } + if r.wifiMonitor != nil { + monitor.Start("close WIFI monitor") + err = E.Append(err, r.wifiMonitor.Close(), func(err error) error { + return E.Cause(err, "close WIFI monitor") + }) + monitor.Finish() + } return err } @@ -376,20 +413,39 @@ func (r *NetworkManager) PackageManager() tun.PackageManager { return r.packageManager } +func (r *NetworkManager) NeedWIFIState() bool { + return r.needWIFIState +} + func (r *NetworkManager) WIFIState() adapter.WIFIState { + r.wifiStateMutex.RLock() + defer r.wifiStateMutex.RUnlock() return r.wifiState } -func (r *NetworkManager) UpdateWIFIState() { - if r.platformInterface != nil { - state := r.platformInterface.ReadWIFIState() - if state != r.wifiState { - r.wifiState = state - if state.SSID != "" { - r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) - } - } +func (r *NetworkManager) onWIFIStateChanged(state adapter.WIFIState) { + r.wifiStateMutex.Lock() + if state == r.wifiState { + r.wifiStateMutex.Unlock() + return } + r.wifiState = state + r.wifiStateMutex.Unlock() + if state.SSID != "" { + r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) + } +} + +func (r *NetworkManager) UpdateWIFIState() { + var state adapter.WIFIState + if r.wifiMonitor != nil { + state = r.wifiMonitor.ReadWIFIState() + } else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() { + state = r.platformInterface.ReadWIFIState() + } else { + return + } + r.onWIFIStateChanged(state) } func (r *NetworkManager) ResetNetwork() { diff --git a/route/router.go b/route/router.go index ae2ecb55..aca65432 100644 --- a/route/router.go +++ b/route/router.go @@ -38,7 +38,6 @@ type Router struct { pauseManager pause.Manager trackers []adapter.ConnectionTracker platformInterface platform.Interface - needWIFIState bool started bool } @@ -57,7 +56,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), - needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } } @@ -113,15 +111,13 @@ func (r *Router) Start(stage adapter.StartStage) error { if cacheContext != nil { cacheContext.Close() } + r.network.Initialize(r.ruleSets) needFindProcess := r.needFindProcess for _, ruleSet := range r.ruleSets { metadata := ruleSet.Metadata() if metadata.ContainsProcessRule { needFindProcess = true } - if metadata.ContainsWIFIRule { - r.needWIFIState = true - } } if needFindProcess { if r.platformInterface != nil { @@ -195,10 +191,6 @@ func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) { return ruleSet, loaded } -func (r *Router) NeedWIFIState() bool { - return r.needWIFIState -} - func (r *Router) Rules() []adapter.Rule { return r.rules } From 743b460e51becdcaf5a5f71be925ef0857ac65e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 7 Dec 2025 15:47:08 +0800 Subject: [PATCH 043/185] Add Windows WI-FI state support --- common/settings/wifi_stub.go | 2 +- common/settings/wifi_windows.go | 144 ++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- 4 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 common/settings/wifi_windows.go diff --git a/common/settings/wifi_stub.go b/common/settings/wifi_stub.go index 98db500f..fd39af9e 100644 --- a/common/settings/wifi_stub.go +++ b/common/settings/wifi_stub.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !windows package settings diff --git a/common/settings/wifi_windows.go b/common/settings/wifi_windows.go new file mode 100644 index 00000000..91b0d479 --- /dev/null +++ b/common/settings/wifi_windows.go @@ -0,0 +1,144 @@ +//go:build windows + +package settings + +import ( + "context" + "fmt" + "strings" + "sync" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/winwlanapi" + + "golang.org/x/sys/windows" +) + +type windowsWIFIMonitor struct { + handle windows.Handle + callback func(adapter.WIFIState) + cancel context.CancelFunc + lastState adapter.WIFIState + mutex sync.Mutex +} + +func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + handle, err := winwlanapi.OpenHandle() + if err != nil { + return nil, err + } + + interfaces, err := winwlanapi.EnumInterfaces(handle) + if err != nil { + winwlanapi.CloseHandle(handle) + return nil, err + } + if len(interfaces) == 0 { + winwlanapi.CloseHandle(handle) + return nil, fmt.Errorf("no wireless interfaces found") + } + + return &windowsWIFIMonitor{ + handle: handle, + callback: callback, + }, nil +} + +func (m *windowsWIFIMonitor) ReadWIFIState() adapter.WIFIState { + interfaces, err := winwlanapi.EnumInterfaces(m.handle) + if err != nil || len(interfaces) == 0 { + return adapter.WIFIState{} + } + + for _, iface := range interfaces { + if iface.InterfaceState != winwlanapi.InterfaceStateConnected { + continue + } + + guid := iface.InterfaceGUID + attrs, err := winwlanapi.QueryCurrentConnection(m.handle, &guid) + if err != nil { + continue + } + + ssidLength := attrs.AssociationAttributes.SSID.Length + if ssidLength == 0 || ssidLength > winwlanapi.Dot11SSIDMaxLength { + continue + } + + ssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLength]) + bssid := formatBSSID(attrs.AssociationAttributes.BSSID) + + return adapter.WIFIState{ + SSID: strings.TrimSpace(ssid), + BSSID: bssid, + } + } + + return adapter.WIFIState{} +} + +func formatBSSID(mac winwlanapi.Dot11MacAddress) string { + return fmt.Sprintf("%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]) +} + +func (m *windowsWIFIMonitor) Start() error { + if m.callback == nil { + return nil + } + + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.lastState = m.ReadWIFIState() + + callbackFunc := func(data *winwlanapi.NotificationData, callbackContext uintptr) uintptr { + if data.NotificationSource != winwlanapi.NotificationSourceACM { + return 0 + } + switch data.NotificationCode { + case winwlanapi.NotificationACMConnectionComplete, + winwlanapi.NotificationACMDisconnected: + m.checkAndNotify() + } + return 0 + } + + callbackPointer := syscall.NewCallback(callbackFunc) + + err := winwlanapi.RegisterNotification(m.handle, winwlanapi.NotificationSourceACM, callbackPointer, 0) + if err != nil { + cancel() + return err + } + + go func() { + <-ctx.Done() + }() + + m.callback(m.lastState) + return nil +} + +func (m *windowsWIFIMonitor) checkAndNotify() { + m.mutex.Lock() + defer m.mutex.Unlock() + + state := m.ReadWIFIState() + if state != m.lastState { + m.lastState = state + if m.callback != nil { + m.callback(state) + } + } +} + +func (m *windowsWIFIMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + winwlanapi.UnregisterNotification(m.handle) + return winwlanapi.CloseHandle(m.handle) +} diff --git a/go.mod b/go.mod index 4145299d..b3728906 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 - github.com/sagernet/sing v0.8.0-beta.6 + github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.5 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index d6370a54..d572feec 100644 --- a/go.sum +++ b/go.sum @@ -161,8 +161,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI= github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.6 h1:GXv1j1xWHihx6ptyOXh0yp4jUqJoNjCqD8d+AI9rnLU= -github.com/sagernet/sing v0.8.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= +github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4= From 5bc0dfa9ddc76b41f1cd4e724003cfa3c8f3a64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 7 Oct 2025 15:40:11 +0800 Subject: [PATCH 044/185] platform: Refactoring libbox to use gRPC-based protocol --- adapter/experimental.go | 4 +- adapter/inbound.go | 3 +- adapter/platform.go | 68 + box.go | 7 +- common/certificate/store.go | 3 +- common/dialer/default.go | 3 +- common/process/searcher.go | 15 +- common/process/searcher_android.go | 17 +- common/process/searcher_darwin.go | 5 +- common/process/searcher_linux.go | 5 +- common/process/searcher_windows.go | 7 +- common/urltest/urltest.go | 10 +- daemon/deprecated.go | 29 + daemon/helper.pb.go | 702 ++++++ daemon/helper.proto | 61 + daemon/instance.go | 133 ++ daemon/platform.go | 9 + daemon/started_service.go | 830 +++++++ daemon/started_service.pb.go | 1906 +++++++++++++++++ daemon/started_service.proto | 204 ++ daemon/started_service_grpc.pb.go | 919 ++++++++ dns/router.go | 3 +- docs/configuration/dns/server/index.zh.md | 96 +- experimental/clashapi/server.go | 10 +- .../clashapi/trafficontrol/tracker.go | 8 +- experimental/libbox/command.go | 11 - experimental/libbox/command_clash_mode.go | 124 -- experimental/libbox/command_client.go | 486 ++++- .../libbox/command_close_connection.go | 54 - experimental/libbox/command_connections.go | 269 --- experimental/libbox/command_conntrack.go | 28 - .../libbox/command_deprecated_report.go | 46 - experimental/libbox/command_group.go | 198 -- experimental/libbox/command_log.go | 160 -- experimental/libbox/command_power.go | 59 - experimental/libbox/command_select.go | 58 - experimental/libbox/command_server.go | 370 ++-- experimental/libbox/command_shared.go | 39 - experimental/libbox/command_status.go | 85 - experimental/libbox/command_system_proxy.go | 80 - experimental/libbox/command_types.go | 276 +++ experimental/libbox/command_urltest.go | 86 - experimental/libbox/config.go | 47 +- experimental/libbox/deprecated.go | 24 - experimental/libbox/http.go | 25 +- experimental/libbox/iterator.go | 6 + experimental/libbox/monitor.go | 2 +- experimental/libbox/platform.go | 6 - experimental/libbox/platform/interface.go | 36 - experimental/libbox/service.go | 202 +- experimental/libbox/service_error.go | 32 - experimental/libbox/service_pause.go | 36 - experimental/libbox/setup.go | 58 +- experimental/libbox/tun.go | 2 +- log/observable.go | 44 +- log/platform.go | 1 - protocol/tailscale/endpoint.go | 7 +- protocol/tailscale/protect_android.go | 4 +- protocol/tailscale/protect_nonandroid.go | 4 +- protocol/tun/inbound.go | 11 +- route/network.go | 13 +- route/platform_searcher.go | 45 + route/route.go | 12 +- route/router.go | 9 +- route/rule/rule_item_package_name.go | 4 +- route/rule/rule_item_user.go | 4 +- service/resolved/resolve1.go | 13 +- 67 files changed, 6131 insertions(+), 2002 deletions(-) create mode 100644 adapter/platform.go create mode 100644 daemon/deprecated.go create mode 100644 daemon/helper.pb.go create mode 100644 daemon/helper.proto create mode 100644 daemon/instance.go create mode 100644 daemon/platform.go create mode 100644 daemon/started_service.go create mode 100644 daemon/started_service.pb.go create mode 100644 daemon/started_service.proto create mode 100644 daemon/started_service_grpc.pb.go delete mode 100644 experimental/libbox/command_clash_mode.go delete mode 100644 experimental/libbox/command_close_connection.go delete mode 100644 experimental/libbox/command_connections.go delete mode 100644 experimental/libbox/command_conntrack.go delete mode 100644 experimental/libbox/command_deprecated_report.go delete mode 100644 experimental/libbox/command_group.go delete mode 100644 experimental/libbox/command_log.go delete mode 100644 experimental/libbox/command_power.go delete mode 100644 experimental/libbox/command_select.go delete mode 100644 experimental/libbox/command_shared.go delete mode 100644 experimental/libbox/command_status.go delete mode 100644 experimental/libbox/command_system_proxy.go create mode 100644 experimental/libbox/command_types.go delete mode 100644 experimental/libbox/command_urltest.go delete mode 100644 experimental/libbox/platform/interface.go delete mode 100644 experimental/libbox/service_error.go delete mode 100644 experimental/libbox/service_pause.go create mode 100644 route/platform_searcher.go diff --git a/adapter/experimental.go b/adapter/experimental.go index de01d7be..d4d37922 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "time" + "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/varbin" ) @@ -14,6 +15,7 @@ type ClashServer interface { ConnectionTracker Mode() string ModeList() []string + SetModeUpdateHook(hook *observable.Subscriber[struct{}]) HistoryStorage() URLTestHistoryStorage } @@ -23,7 +25,7 @@ type URLTestHistory struct { } type URLTestHistoryStorage interface { - SetHook(hook chan<- struct{}) + SetHook(hook *observable.Subscriber[struct{}]) LoadURLTestHistory(tag string) *URLTestHistory DeleteURLTestHistory(tag string) StoreURLTestHistory(tag string, history *URLTestHistory) diff --git a/adapter/inbound.go b/adapter/inbound.go index de30149f..1941df5b 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -5,7 +5,6 @@ import ( "net/netip" "time" - "github.com/sagernet/sing-box/common/process" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -85,7 +84,7 @@ type InboundContext struct { DestinationAddresses []netip.Addr SourceGeoIPCode string GeoIPCode string - ProcessInfo *process.Info + ProcessInfo *ConnectionOwner QueryType uint16 FakeIP bool diff --git a/adapter/platform.go b/adapter/platform.go new file mode 100644 index 00000000..61dc7b44 --- /dev/null +++ b/adapter/platform.go @@ -0,0 +1,68 @@ +package adapter + +import ( + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" +) + +type PlatformInterface interface { + Initialize(networkManager NetworkManager) error + + UsePlatformAutoDetectInterfaceControl() bool + AutoDetectInterfaceControl(fd int) error + + UsePlatformInterface() bool + OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) + + UsePlatformDefaultInterfaceMonitor() bool + CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor + + UsePlatformNetworkInterfaces() bool + NetworkInterfaces() ([]NetworkInterface, error) + + UnderNetworkExtension() bool + NetworkExtensionIncludeAllNetworks() bool + + ClearDNSCache() + RequestPermissionForWIFIState() error + ReadWIFIState() WIFIState + SystemCertificates() []string + + UsePlatformConnectionOwnerFinder() bool + FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error) + + UsePlatformNotification() bool + SendNotification(notification *Notification) error +} + +type FindConnectionOwnerRequest struct { + IpProtocol int32 + SourceAddress string + SourcePort int32 + DestinationAddress string + DestinationPort int32 +} + +type ConnectionOwner struct { + ProcessID uint32 + UserId int32 + UserName string + ProcessPath string + AndroidPackageName string +} + +type Notification struct { + Identifier string + TypeName string + TypeID int32 + Title string + Subtitle string + Body string + OpenURL string +} + +type SystemProxyStatus struct { + Available bool + Enabled bool +} diff --git a/box.go b/box.go index a84437ef..1c168820 100644 --- a/box.go +++ b/box.go @@ -22,7 +22,6 @@ import ( "github.com/sagernet/sing-box/dns/transport/local" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/direct" @@ -139,7 +138,7 @@ func New(options Options) (*Box, error) { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var defaultLogWriter io.Writer if platformInterface != nil { defaultLogWriter = io.Discard @@ -527,3 +526,7 @@ func (s *Box) Inbound() adapter.InboundManager { func (s *Box) Outbound() adapter.OutboundManager { return s.outbound } + +func (s *Box) LogFactory() log.Factory { + return s.logFactory +} diff --git a/common/certificate/store.go b/common/certificate/store.go index ee127278..82ce8e29 100644 --- a/common/certificate/store.go +++ b/common/certificate/store.go @@ -12,7 +12,6 @@ import ( "github.com/sagernet/fswatch" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" @@ -36,7 +35,7 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific switch options.Store { case C.CertificateStoreSystem, "": systemPool = x509.NewCertPool() - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var systemValid bool if platformInterface != nil { for _, cert := range platformInterface.SystemCertificates() { diff --git a/common/dialer/default.go b/common/dialer/default.go index 23754e0a..d38ad487 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -12,7 +12,6 @@ import ( "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/listener" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" @@ -49,7 +48,7 @@ type DefaultDialer struct { func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { networkManager := service.FromContext[adapter.NetworkManager](ctx) - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var ( dialer net.Dialer diff --git a/common/process/searcher.go b/common/process/searcher.go index d525b3c1..1af2c2bd 100644 --- a/common/process/searcher.go +++ b/common/process/searcher.go @@ -5,6 +5,7 @@ import ( "net/netip" "os/user" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-tun" E "github.com/sagernet/sing/common/exceptions" @@ -12,7 +13,7 @@ import ( ) type Searcher interface { - FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) + FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) } var ErrNotFound = E.New("process not found") @@ -22,15 +23,7 @@ type Config struct { PackageManager tun.PackageManager } -type Info struct { - ProcessID uint32 - ProcessPath string - PackageName string - User string - UserId int32 -} - -func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { info, err := searcher.FindProcessInfo(ctx, network, source, destination) if err != nil { return nil, err @@ -38,7 +31,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou if info.UserId != -1 { osUser, _ := user.LookupId(F.ToString(info.UserId)) if osUser != nil { - info.User = osUser.Username + info.UserName = osUser.Username } } return info, nil diff --git a/common/process/searcher_android.go b/common/process/searcher_android.go index e1835b47..ac9550ce 100644 --- a/common/process/searcher_android.go +++ b/common/process/searcher_android.go @@ -4,6 +4,7 @@ import ( "context" "net/netip" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-tun" ) @@ -17,22 +18,22 @@ func NewSearcher(config Config) (Searcher, error) { return &androidSearcher{config.PackageManager}, nil } -func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { _, uid, err := resolveSocketByNetlink(network, source, destination) if err != nil { return nil, err } if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded { - return &Info{ - UserId: int32(uid), - PackageName: sharedPackage, + return &adapter.ConnectionOwner{ + UserId: int32(uid), + AndroidPackageName: sharedPackage, }, nil } if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded { - return &Info{ - UserId: int32(uid), - PackageName: packageName, + return &adapter.ConnectionOwner{ + UserId: int32(uid), + AndroidPackageName: packageName, }, nil } - return &Info{UserId: int32(uid)}, nil + return &adapter.ConnectionOwner{UserId: int32(uid)}, nil } diff --git a/common/process/searcher_darwin.go b/common/process/searcher_darwin.go index 5c1addd5..03428cc8 100644 --- a/common/process/searcher_darwin.go +++ b/common/process/searcher_darwin.go @@ -10,6 +10,7 @@ import ( "syscall" "unsafe" + "github.com/sagernet/sing-box/adapter" N "github.com/sagernet/sing/common/network" "golang.org/x/sys/unix" @@ -23,12 +24,12 @@ func NewSearcher(_ Config) (Searcher, error) { return &darwinSearcher{}, nil } -func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { processName, err := findProcessName(network, source.Addr(), int(source.Port())) if err != nil { return nil, err } - return &Info{ProcessPath: processName, UserId: -1}, nil + return &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil } var structSize = func() int { diff --git a/common/process/searcher_linux.go b/common/process/searcher_linux.go index 39470205..86d37d7c 100644 --- a/common/process/searcher_linux.go +++ b/common/process/searcher_linux.go @@ -6,6 +6,7 @@ import ( "context" "net/netip" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/log" ) @@ -19,7 +20,7 @@ func NewSearcher(config Config) (Searcher, error) { return &linuxSearcher{config.Logger}, nil } -func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { inode, uid, err := resolveSocketByNetlink(network, source, destination) if err != nil { return nil, err @@ -28,7 +29,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, sou if err != nil { s.logger.DebugContext(ctx, "find process path: ", err) } - return &Info{ + return &adapter.ConnectionOwner{ UserId: int32(uid), ProcessPath: processPath, }, nil diff --git a/common/process/searcher_windows.go b/common/process/searcher_windows.go index b7d89dda..ac95e0ce 100644 --- a/common/process/searcher_windows.go +++ b/common/process/searcher_windows.go @@ -5,6 +5,7 @@ import ( "net/netip" "syscall" + "github.com/sagernet/sing-box/adapter" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/winiphlpapi" @@ -27,16 +28,16 @@ func initWin32API() error { return winiphlpapi.LoadExtendedTable() } -func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { +func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { pid, err := winiphlpapi.FindPid(network, source) if err != nil { return nil, err } path, err := getProcessPath(pid) if err != nil { - return &Info{ProcessID: pid, UserId: -1}, err + return &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err } - return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil + return &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil } func getProcessPath(pid uint32) (string, error) { diff --git a/common/urltest/urltest.go b/common/urltest/urltest.go index 016f4a50..29d790e4 100644 --- a/common/urltest/urltest.go +++ b/common/urltest/urltest.go @@ -14,6 +14,7 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" + "github.com/sagernet/sing/common/observable" ) var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil) @@ -21,7 +22,7 @@ var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil) type HistoryStorage struct { access sync.RWMutex delayHistory map[string]*adapter.URLTestHistory - updateHook chan<- struct{} + updateHook *observable.Subscriber[struct{}] } func NewHistoryStorage() *HistoryStorage { @@ -30,7 +31,7 @@ func NewHistoryStorage() *HistoryStorage { } } -func (s *HistoryStorage) SetHook(hook chan<- struct{}) { +func (s *HistoryStorage) SetHook(hook *observable.Subscriber[struct{}]) { s.updateHook = hook } @@ -60,10 +61,7 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTes func (s *HistoryStorage) notifyUpdated() { updateHook := s.updateHook if updateHook != nil { - select { - case updateHook <- struct{}{}: - default: - } + updateHook.Emit(struct{}{}) } } diff --git a/daemon/deprecated.go b/daemon/deprecated.go new file mode 100644 index 00000000..6f23db99 --- /dev/null +++ b/daemon/deprecated.go @@ -0,0 +1,29 @@ +package daemon + +import ( + "sync" + + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing/common" +) + +var _ deprecated.Manager = (*deprecatedManager)(nil) + +type deprecatedManager struct { + access sync.Mutex + notes []deprecated.Note +} + +func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) { + m.access.Lock() + defer m.access.Unlock() + m.notes = common.Uniq(append(m.notes, feature)) +} + +func (m *deprecatedManager) Get() []deprecated.Note { + m.access.Lock() + defer m.access.Unlock() + notes := m.notes + m.notes = nil + return notes +} diff --git a/daemon/helper.pb.go b/daemon/helper.pb.go new file mode 100644 index 00000000..9a2641c5 --- /dev/null +++ b/daemon/helper.pb.go @@ -0,0 +1,702 @@ +package daemon + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SubscribeHelperRequestRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AcceptGetWIFIStateRequests bool `protobuf:"varint,1,opt,name=acceptGetWIFIStateRequests,proto3" json:"acceptGetWIFIStateRequests,omitempty"` + AcceptFindConnectionOwnerRequests bool `protobuf:"varint,2,opt,name=acceptFindConnectionOwnerRequests,proto3" json:"acceptFindConnectionOwnerRequests,omitempty"` + AcceptSendNotificationRequests bool `protobuf:"varint,3,opt,name=acceptSendNotificationRequests,proto3" json:"acceptSendNotificationRequests,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeHelperRequestRequest) Reset() { + *x = SubscribeHelperRequestRequest{} + mi := &file_daemon_helper_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeHelperRequestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeHelperRequestRequest) ProtoMessage() {} + +func (x *SubscribeHelperRequestRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeHelperRequestRequest.ProtoReflect.Descriptor instead. +func (*SubscribeHelperRequestRequest) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{0} +} + +func (x *SubscribeHelperRequestRequest) GetAcceptGetWIFIStateRequests() bool { + if x != nil { + return x.AcceptGetWIFIStateRequests + } + return false +} + +func (x *SubscribeHelperRequestRequest) GetAcceptFindConnectionOwnerRequests() bool { + if x != nil { + return x.AcceptFindConnectionOwnerRequests + } + return false +} + +func (x *SubscribeHelperRequestRequest) GetAcceptSendNotificationRequests() bool { + if x != nil { + return x.AcceptSendNotificationRequests + } + return false +} + +type HelperRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // Types that are valid to be assigned to Request: + // + // *HelperRequest_GetWIFIState + // *HelperRequest_FindConnectionOwner + // *HelperRequest_SendNotification + Request isHelperRequest_Request `protobuf_oneof:"request"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelperRequest) Reset() { + *x = HelperRequest{} + mi := &file_daemon_helper_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelperRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelperRequest) ProtoMessage() {} + +func (x *HelperRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelperRequest.ProtoReflect.Descriptor instead. +func (*HelperRequest) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{1} +} + +func (x *HelperRequest) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *HelperRequest) GetRequest() isHelperRequest_Request { + if x != nil { + return x.Request + } + return nil +} + +func (x *HelperRequest) GetGetWIFIState() *emptypb.Empty { + if x != nil { + if x, ok := x.Request.(*HelperRequest_GetWIFIState); ok { + return x.GetWIFIState + } + } + return nil +} + +func (x *HelperRequest) GetFindConnectionOwner() *FindConnectionOwnerRequest { + if x != nil { + if x, ok := x.Request.(*HelperRequest_FindConnectionOwner); ok { + return x.FindConnectionOwner + } + } + return nil +} + +func (x *HelperRequest) GetSendNotification() *Notification { + if x != nil { + if x, ok := x.Request.(*HelperRequest_SendNotification); ok { + return x.SendNotification + } + } + return nil +} + +type isHelperRequest_Request interface { + isHelperRequest_Request() +} + +type HelperRequest_GetWIFIState struct { + GetWIFIState *emptypb.Empty `protobuf:"bytes,2,opt,name=getWIFIState,proto3,oneof"` +} + +type HelperRequest_FindConnectionOwner struct { + FindConnectionOwner *FindConnectionOwnerRequest `protobuf:"bytes,3,opt,name=findConnectionOwner,proto3,oneof"` +} + +type HelperRequest_SendNotification struct { + SendNotification *Notification `protobuf:"bytes,4,opt,name=sendNotification,proto3,oneof"` +} + +func (*HelperRequest_GetWIFIState) isHelperRequest_Request() {} + +func (*HelperRequest_FindConnectionOwner) isHelperRequest_Request() {} + +func (*HelperRequest_SendNotification) isHelperRequest_Request() {} + +type FindConnectionOwnerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + IpProtocol int32 `protobuf:"varint,1,opt,name=ipProtocol,proto3" json:"ipProtocol,omitempty"` + SourceAddress string `protobuf:"bytes,2,opt,name=sourceAddress,proto3" json:"sourceAddress,omitempty"` + SourcePort int32 `protobuf:"varint,3,opt,name=sourcePort,proto3" json:"sourcePort,omitempty"` + DestinationAddress string `protobuf:"bytes,4,opt,name=destinationAddress,proto3" json:"destinationAddress,omitempty"` + DestinationPort int32 `protobuf:"varint,5,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FindConnectionOwnerRequest) Reset() { + *x = FindConnectionOwnerRequest{} + mi := &file_daemon_helper_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FindConnectionOwnerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FindConnectionOwnerRequest) ProtoMessage() {} + +func (x *FindConnectionOwnerRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FindConnectionOwnerRequest.ProtoReflect.Descriptor instead. +func (*FindConnectionOwnerRequest) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{2} +} + +func (x *FindConnectionOwnerRequest) GetIpProtocol() int32 { + if x != nil { + return x.IpProtocol + } + return 0 +} + +func (x *FindConnectionOwnerRequest) GetSourceAddress() string { + if x != nil { + return x.SourceAddress + } + return "" +} + +func (x *FindConnectionOwnerRequest) GetSourcePort() int32 { + if x != nil { + return x.SourcePort + } + return 0 +} + +func (x *FindConnectionOwnerRequest) GetDestinationAddress() string { + if x != nil { + return x.DestinationAddress + } + return "" +} + +func (x *FindConnectionOwnerRequest) GetDestinationPort() int32 { + if x != nil { + return x.DestinationPort + } + return 0 +} + +type Notification struct { + state protoimpl.MessageState `protogen:"open.v1"` + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + TypeName string `protobuf:"bytes,2,opt,name=typeName,proto3" json:"typeName,omitempty"` + TypeId int32 `protobuf:"varint,3,opt,name=typeId,proto3" json:"typeId,omitempty"` + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + Subtitle string `protobuf:"bytes,5,opt,name=subtitle,proto3" json:"subtitle,omitempty"` + Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` + OpenURL string `protobuf:"bytes,7,opt,name=openURL,proto3" json:"openURL,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Notification) Reset() { + *x = Notification{} + mi := &file_daemon_helper_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Notification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Notification) ProtoMessage() {} + +func (x *Notification) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Notification.ProtoReflect.Descriptor instead. +func (*Notification) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{3} +} + +func (x *Notification) GetIdentifier() string { + if x != nil { + return x.Identifier + } + return "" +} + +func (x *Notification) GetTypeName() string { + if x != nil { + return x.TypeName + } + return "" +} + +func (x *Notification) GetTypeId() int32 { + if x != nil { + return x.TypeId + } + return 0 +} + +func (x *Notification) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Notification) GetSubtitle() string { + if x != nil { + return x.Subtitle + } + return "" +} + +func (x *Notification) GetBody() string { + if x != nil { + return x.Body + } + return "" +} + +func (x *Notification) GetOpenURL() string { + if x != nil { + return x.OpenURL + } + return "" +} + +type HelperResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // Types that are valid to be assigned to Response: + // + // *HelperResponse_WifiState + // *HelperResponse_Error + // *HelperResponse_ConnectionOwner + Response isHelperResponse_Response `protobuf_oneof:"response"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelperResponse) Reset() { + *x = HelperResponse{} + mi := &file_daemon_helper_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelperResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelperResponse) ProtoMessage() {} + +func (x *HelperResponse) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelperResponse.ProtoReflect.Descriptor instead. +func (*HelperResponse) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{4} +} + +func (x *HelperResponse) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *HelperResponse) GetResponse() isHelperResponse_Response { + if x != nil { + return x.Response + } + return nil +} + +func (x *HelperResponse) GetWifiState() *WIFIState { + if x != nil { + if x, ok := x.Response.(*HelperResponse_WifiState); ok { + return x.WifiState + } + } + return nil +} + +func (x *HelperResponse) GetError() string { + if x != nil { + if x, ok := x.Response.(*HelperResponse_Error); ok { + return x.Error + } + } + return "" +} + +func (x *HelperResponse) GetConnectionOwner() *ConnectionOwner { + if x != nil { + if x, ok := x.Response.(*HelperResponse_ConnectionOwner); ok { + return x.ConnectionOwner + } + } + return nil +} + +type isHelperResponse_Response interface { + isHelperResponse_Response() +} + +type HelperResponse_WifiState struct { + WifiState *WIFIState `protobuf:"bytes,2,opt,name=wifiState,proto3,oneof"` +} + +type HelperResponse_Error struct { + Error string `protobuf:"bytes,3,opt,name=error,proto3,oneof"` +} + +type HelperResponse_ConnectionOwner struct { + ConnectionOwner *ConnectionOwner `protobuf:"bytes,4,opt,name=connectionOwner,proto3,oneof"` +} + +func (*HelperResponse_WifiState) isHelperResponse_Response() {} + +func (*HelperResponse_Error) isHelperResponse_Response() {} + +func (*HelperResponse_ConnectionOwner) isHelperResponse_Response() {} + +type ConnectionOwner struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` + UserName string `protobuf:"bytes,2,opt,name=userName,proto3" json:"userName,omitempty"` + ProcessPath string `protobuf:"bytes,3,opt,name=processPath,proto3" json:"processPath,omitempty"` + AndroidPackageName string `protobuf:"bytes,4,opt,name=androidPackageName,proto3" json:"androidPackageName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectionOwner) Reset() { + *x = ConnectionOwner{} + mi := &file_daemon_helper_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectionOwner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectionOwner) ProtoMessage() {} + +func (x *ConnectionOwner) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectionOwner.ProtoReflect.Descriptor instead. +func (*ConnectionOwner) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{5} +} + +func (x *ConnectionOwner) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ConnectionOwner) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *ConnectionOwner) GetProcessPath() string { + if x != nil { + return x.ProcessPath + } + return "" +} + +func (x *ConnectionOwner) GetAndroidPackageName() string { + if x != nil { + return x.AndroidPackageName + } + return "" +} + +type WIFIState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ssid string `protobuf:"bytes,1,opt,name=ssid,proto3" json:"ssid,omitempty"` + Bssid string `protobuf:"bytes,2,opt,name=bssid,proto3" json:"bssid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WIFIState) Reset() { + *x = WIFIState{} + mi := &file_daemon_helper_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WIFIState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WIFIState) ProtoMessage() {} + +func (x *WIFIState) ProtoReflect() protoreflect.Message { + mi := &file_daemon_helper_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WIFIState.ProtoReflect.Descriptor instead. +func (*WIFIState) Descriptor() ([]byte, []int) { + return file_daemon_helper_proto_rawDescGZIP(), []int{6} +} + +func (x *WIFIState) GetSsid() string { + if x != nil { + return x.Ssid + } + return "" +} + +func (x *WIFIState) GetBssid() string { + if x != nil { + return x.Bssid + } + return "" +} + +var File_daemon_helper_proto protoreflect.FileDescriptor + +const file_daemon_helper_proto_rawDesc = "" + + "\n" + + "\x13daemon/helper.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xf5\x01\n" + + "\x1dSubscribeHelperRequestRequest\x12>\n" + + "\x1aacceptGetWIFIStateRequests\x18\x01 \x01(\bR\x1aacceptGetWIFIStateRequests\x12L\n" + + "!acceptFindConnectionOwnerRequests\x18\x02 \x01(\bR!acceptFindConnectionOwnerRequests\x12F\n" + + "\x1eacceptSendNotificationRequests\x18\x03 \x01(\bR\x1eacceptSendNotificationRequests\"\x84\x02\n" + + "\rHelperRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12<\n" + + "\fgetWIFIState\x18\x02 \x01(\v2\x16.google.protobuf.EmptyH\x00R\fgetWIFIState\x12V\n" + + "\x13findConnectionOwner\x18\x03 \x01(\v2\".daemon.FindConnectionOwnerRequestH\x00R\x13findConnectionOwner\x12B\n" + + "\x10sendNotification\x18\x04 \x01(\v2\x14.daemon.NotificationH\x00R\x10sendNotificationB\t\n" + + "\arequest\"\xdc\x01\n" + + "\x1aFindConnectionOwnerRequest\x12\x1e\n" + + "\n" + + "ipProtocol\x18\x01 \x01(\x05R\n" + + "ipProtocol\x12$\n" + + "\rsourceAddress\x18\x02 \x01(\tR\rsourceAddress\x12\x1e\n" + + "\n" + + "sourcePort\x18\x03 \x01(\x05R\n" + + "sourcePort\x12.\n" + + "\x12destinationAddress\x18\x04 \x01(\tR\x12destinationAddress\x12(\n" + + "\x0fdestinationPort\x18\x05 \x01(\x05R\x0fdestinationPort\"\xc2\x01\n" + + "\fNotification\x12\x1e\n" + + "\n" + + "identifier\x18\x01 \x01(\tR\n" + + "identifier\x12\x1a\n" + + "\btypeName\x18\x02 \x01(\tR\btypeName\x12\x16\n" + + "\x06typeId\x18\x03 \x01(\x05R\x06typeId\x12\x14\n" + + "\x05title\x18\x04 \x01(\tR\x05title\x12\x1a\n" + + "\bsubtitle\x18\x05 \x01(\tR\bsubtitle\x12\x12\n" + + "\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" + + "\aopenURL\x18\a \x01(\tR\aopenURL\"\xbc\x01\n" + + "\x0eHelperResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x121\n" + + "\twifiState\x18\x02 \x01(\v2\x11.daemon.WIFIStateH\x00R\twifiState\x12\x16\n" + + "\x05error\x18\x03 \x01(\tH\x00R\x05error\x12C\n" + + "\x0fconnectionOwner\x18\x04 \x01(\v2\x17.daemon.ConnectionOwnerH\x00R\x0fconnectionOwnerB\n" + + "\n" + + "\bresponse\"\x97\x01\n" + + "\x0fConnectionOwner\x12\x16\n" + + "\x06userId\x18\x01 \x01(\x05R\x06userId\x12\x1a\n" + + "\buserName\x18\x02 \x01(\tR\buserName\x12 \n" + + "\vprocessPath\x18\x03 \x01(\tR\vprocessPath\x12.\n" + + "\x12androidPackageName\x18\x04 \x01(\tR\x12androidPackageName\"5\n" + + "\tWIFIState\x12\x12\n" + + "\x04ssid\x18\x01 \x01(\tR\x04ssid\x12\x14\n" + + "\x05bssid\x18\x02 \x01(\tR\x05bssidB%Z#github.com/sagernet/sing-box/daemonb\x06proto3" + +var ( + file_daemon_helper_proto_rawDescOnce sync.Once + file_daemon_helper_proto_rawDescData []byte +) + +func file_daemon_helper_proto_rawDescGZIP() []byte { + file_daemon_helper_proto_rawDescOnce.Do(func() { + file_daemon_helper_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc))) + }) + return file_daemon_helper_proto_rawDescData +} + +var ( + file_daemon_helper_proto_msgTypes = make([]protoimpl.MessageInfo, 7) + file_daemon_helper_proto_goTypes = []any{ + (*SubscribeHelperRequestRequest)(nil), // 0: daemon.SubscribeHelperRequestRequest + (*HelperRequest)(nil), // 1: daemon.HelperRequest + (*FindConnectionOwnerRequest)(nil), // 2: daemon.FindConnectionOwnerRequest + (*Notification)(nil), // 3: daemon.Notification + (*HelperResponse)(nil), // 4: daemon.HelperResponse + (*ConnectionOwner)(nil), // 5: daemon.ConnectionOwner + (*WIFIState)(nil), // 6: daemon.WIFIState + (*emptypb.Empty)(nil), // 7: google.protobuf.Empty + } +) + +var file_daemon_helper_proto_depIdxs = []int32{ + 7, // 0: daemon.HelperRequest.getWIFIState:type_name -> google.protobuf.Empty + 2, // 1: daemon.HelperRequest.findConnectionOwner:type_name -> daemon.FindConnectionOwnerRequest + 3, // 2: daemon.HelperRequest.sendNotification:type_name -> daemon.Notification + 6, // 3: daemon.HelperResponse.wifiState:type_name -> daemon.WIFIState + 5, // 4: daemon.HelperResponse.connectionOwner:type_name -> daemon.ConnectionOwner + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_daemon_helper_proto_init() } +func file_daemon_helper_proto_init() { + if File_daemon_helper_proto != nil { + return + } + file_daemon_helper_proto_msgTypes[1].OneofWrappers = []any{ + (*HelperRequest_GetWIFIState)(nil), + (*HelperRequest_FindConnectionOwner)(nil), + (*HelperRequest_SendNotification)(nil), + } + file_daemon_helper_proto_msgTypes[4].OneofWrappers = []any{ + (*HelperResponse_WifiState)(nil), + (*HelperResponse_Error)(nil), + (*HelperResponse_ConnectionOwner)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)), + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_daemon_helper_proto_goTypes, + DependencyIndexes: file_daemon_helper_proto_depIdxs, + MessageInfos: file_daemon_helper_proto_msgTypes, + }.Build() + File_daemon_helper_proto = out.File + file_daemon_helper_proto_goTypes = nil + file_daemon_helper_proto_depIdxs = nil +} diff --git a/daemon/helper.proto b/daemon/helper.proto new file mode 100644 index 00000000..8cf28075 --- /dev/null +++ b/daemon/helper.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package daemon; +option go_package = "github.com/sagernet/sing-box/daemon"; + +import "google/protobuf/empty.proto"; + +message SubscribeHelperRequestRequest { + bool acceptGetWIFIStateRequests = 1; + bool acceptFindConnectionOwnerRequests = 2; + bool acceptSendNotificationRequests = 3; +} + +message HelperRequest { + int64 id = 1; + oneof request { + google.protobuf.Empty getWIFIState = 2; + FindConnectionOwnerRequest findConnectionOwner = 3; + Notification sendNotification = 4; + } +} + +message FindConnectionOwnerRequest { + int32 ipProtocol = 1; + string sourceAddress = 2; + int32 sourcePort = 3; + string destinationAddress = 4; + int32 destinationPort = 5; +} + +message Notification { + string identifier = 1; + string typeName = 2; + int32 typeId = 3; + string title = 4; + string subtitle = 5; + string body = 6; + string openURL = 7; +} + +message HelperResponse { + int64 id = 1; + oneof response { + WIFIState wifiState = 2; + string error = 3; + ConnectionOwner connectionOwner = 4; + } +} + +message ConnectionOwner { + int32 userId = 1; + string userName = 2; + string processPath = 3; + string androidPackageName = 4; +} + +message WIFIState { + string ssid = 1; + string bssid = 2; +} + diff --git a/daemon/instance.go b/daemon/instance.go new file mode 100644 index 00000000..9bdce84c --- /dev/null +++ b/daemon/instance.go @@ -0,0 +1,133 @@ +package daemon + +import ( + "bytes" + "context" + + "github.com/sagernet/sing-box" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/urltest" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/service" + "github.com/sagernet/sing/service/pause" +) + +type Instance struct { + ctx context.Context + cancel context.CancelFunc + instance *box.Box + clashServer adapter.ClashServer + cacheFile adapter.CacheFile + pauseManager pause.Manager + urlTestHistoryStorage *urltest.HistoryStorage +} + +func (s *StartedService) CheckConfig(configContent string) error { + options, err := parseConfig(s.ctx, configContent) + if err != nil { + return err + } + ctx, cancel := context.WithCancel(s.ctx) + defer cancel() + instance, err := box.New(box.Options{ + Context: ctx, + Options: options, + }) + if err == nil { + instance.Close() + } + return err +} + +func (s *StartedService) FormatConfig(configContent string) (string, error) { + options, err := parseConfig(s.ctx, configContent) + if err != nil { + return "", err + } + var buffer bytes.Buffer + encoder := json.NewEncoder(&buffer) + encoder.SetIndent("", " ") + err = encoder.Encode(options) + if err != nil { + return "", err + } + return buffer.String(), nil +} + +type OverrideOptions struct { + AutoRedirect bool + IncludePackage []string + ExcludePackage []string +} + +func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) { + ctx := s.ctx + service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) + ctx, cancel := context.WithCancel(include.Context(ctx)) + options, err := parseConfig(ctx, profileContent) + if err != nil { + cancel() + return nil, err + } + if overrideOptions != nil { + for _, inbound := range options.Inbounds { + if tunInboundOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN { + tunInboundOptions.AutoRedirect = overrideOptions.AutoRedirect + tunInboundOptions.IncludePackage = append(tunInboundOptions.IncludePackage, overrideOptions.IncludePackage...) + tunInboundOptions.ExcludePackage = append(tunInboundOptions.ExcludePackage, overrideOptions.ExcludePackage...) + break + } + } + } + urlTestHistoryStorage := urltest.NewHistoryStorage() + ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) + i := &Instance{ + ctx: ctx, + cancel: cancel, + urlTestHistoryStorage: urlTestHistoryStorage, + } + boxInstance, err := box.New(box.Options{ + Context: ctx, + Options: options, + PlatformLogWriter: s, + }) + if err != nil { + cancel() + return nil, err + } + i.instance = boxInstance + i.clashServer = service.FromContext[adapter.ClashServer](ctx) + i.pauseManager = service.FromContext[pause.Manager](ctx) + i.cacheFile = service.FromContext[adapter.CacheFile](ctx) + return i, nil +} + +func (i *Instance) Start() error { + return i.instance.Start() +} + +func (i *Instance) Close() error { + i.cancel() + i.urlTestHistoryStorage.Close() + return i.instance.Close() +} + +func (i *Instance) Box() *box.Box { + return i.instance +} + +func (i *Instance) PauseManager() pause.Manager { + return i.pauseManager +} + +func parseConfig(ctx context.Context, configContent string) (option.Options, error) { + options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent)) + if err != nil { + return option.Options{}, E.Cause(err, "decode config") + } + return options, nil +} diff --git a/daemon/platform.go b/daemon/platform.go new file mode 100644 index 00000000..37906aff --- /dev/null +++ b/daemon/platform.go @@ -0,0 +1,9 @@ +package daemon + +type PlatformHandler interface { + ServiceStop() error + ServiceReload() error + SystemProxyStatus() (*SystemProxyStatus, error) + SetSystemProxyEnabled(enabled bool) error + WriteDebugMessage(message string) +} diff --git a/daemon/started_service.go b/daemon/started_service.go new file mode 100644 index 00000000..8b926b27 --- /dev/null +++ b/daemon/started_service.go @@ -0,0 +1,830 @@ +package daemon + +import ( + "context" + "os" + "runtime" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/conntrack" + "github.com/sagernet/sing-box/common/urltest" + "github.com/sagernet/sing-box/experimental/clashapi" + "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" + "github.com/sagernet/sing-box/experimental/deprecated" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/protocol/group" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/batch" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/memory" + "github.com/sagernet/sing/common/observable" + "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" + + "github.com/gofrs/uuid/v5" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" +) + +var _ StartedServiceServer = (*StartedService)(nil) + +type StartedService struct { + ctx context.Context + // platform adapter.PlatformInterface + handler PlatformHandler + debug bool + logMaxLines int + // workingDirectory string + // tempDirectory string + // userID int + // groupID int + // systemProxyEnabled bool + serviceAccess sync.RWMutex + serviceStatus *ServiceStatus + serviceStatusSubscriber *observable.Subscriber[*ServiceStatus] + serviceStatusObserver *observable.Observer[*ServiceStatus] + logAccess sync.RWMutex + logLines list.List[*log.Entry] + logSubscriber *observable.Subscriber[*log.Entry] + logObserver *observable.Observer[*log.Entry] + instance *Instance + urlTestSubscriber *observable.Subscriber[struct{}] + urlTestObserver *observable.Observer[struct{}] + urlTestHistoryStorage *urltest.HistoryStorage + clashModeSubscriber *observable.Subscriber[struct{}] + clashModeObserver *observable.Observer[struct{}] +} + +type ServiceOptions struct { + Context context.Context + // Platform adapter.PlatformInterface + Handler PlatformHandler + Debug bool + LogMaxLines int + // WorkingDirectory string + // TempDirectory string + // UserID int + // GroupID int + // SystemProxyEnabled bool +} + +func NewStartedService(options ServiceOptions) *StartedService { + s := &StartedService{ + ctx: options.Context, + // platform: options.Platform, + handler: options.Handler, + debug: options.Debug, + logMaxLines: options.LogMaxLines, + // workingDirectory: options.WorkingDirectory, + // tempDirectory: options.TempDirectory, + // userID: options.UserID, + // groupID: options.GroupID, + // systemProxyEnabled: options.SystemProxyEnabled, + serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE}, + serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4), + logSubscriber: observable.NewSubscriber[*log.Entry](128), + urlTestSubscriber: observable.NewSubscriber[struct{}](1), + urlTestHistoryStorage: urltest.NewHistoryStorage(), + clashModeSubscriber: observable.NewSubscriber[struct{}](1), + } + s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2) + s.logObserver = observable.NewObserver(s.logSubscriber, 64) + s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1) + s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1) + return s +} + +func (s *StartedService) resetLogs() { + s.logAccess.Lock() + s.logLines = list.List[*log.Entry]{} + s.logAccess.Unlock() + s.logSubscriber.Emit(nil) +} + +func (s *StartedService) updateStatus(newStatus ServiceStatus_Type) { + statusObject := &ServiceStatus{Status: newStatus} + s.serviceStatusSubscriber.Emit(statusObject) + s.serviceStatus = statusObject +} + +func (s *StartedService) updateStatusError(err error) error { + statusObject := &ServiceStatus{Status: ServiceStatus_FATAL, ErrorMessage: err.Error()} + s.serviceStatusSubscriber.Emit(statusObject) + s.serviceStatus = statusObject + s.serviceAccess.Unlock() + return err +} + +func (s *StartedService) waitForStarted(ctx context.Context) error { + s.serviceAccess.RLock() + currentStatus := s.serviceStatus.Status + s.serviceAccess.RUnlock() + + switch currentStatus { + case ServiceStatus_STARTED: + return nil + case ServiceStatus_STARTING: + default: + return os.ErrInvalid + } + + subscription, done, err := s.serviceStatusObserver.Subscribe() + if err != nil { + return err + } + defer s.serviceStatusObserver.UnSubscribe(subscription) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-s.ctx.Done(): + return s.ctx.Err() + case status := <-subscription: + switch status.Status { + case ServiceStatus_STARTED: + return nil + case ServiceStatus_FATAL: + return E.New(status.ErrorMessage) + case ServiceStatus_IDLE, ServiceStatus_STOPPING: + return os.ErrInvalid + } + case <-done: + return os.ErrClosed + } + } +} + +func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error { + s.serviceAccess.Lock() + switch s.serviceStatus.Status { + case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING: + default: + s.serviceAccess.Unlock() + return os.ErrInvalid + } + oldInstance := s.instance + if oldInstance != nil { + s.updateStatus(ServiceStatus_STOPPING) + s.serviceAccess.Unlock() + _ = oldInstance.Close() + s.serviceAccess.Lock() + } + s.updateStatus(ServiceStatus_STARTING) + s.resetLogs() + instance, err := s.newInstance(profileContent, options) + if err != nil { + return s.updateStatusError(err) + } + s.instance = instance + instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber) + if instance.clashServer != nil { + instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber) + } + s.serviceAccess.Unlock() + err = instance.Start() + s.serviceAccess.Lock() + if s.serviceStatus.Status != ServiceStatus_STARTING { + s.serviceAccess.Unlock() + return nil + } + if err != nil { + return s.updateStatusError(err) + } + s.updateStatus(ServiceStatus_STARTED) + s.serviceAccess.Unlock() + runtime.GC() + return nil +} + +func (s *StartedService) CloseService() error { + s.serviceAccess.Lock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.Unlock() + return os.ErrInvalid + } + s.updateStatus(ServiceStatus_STOPPING) + if s.instance != nil { + err := s.instance.Close() + if err != nil { + return s.updateStatusError(err) + } + } + s.instance = nil + s.updateStatus(ServiceStatus_IDLE) + s.serviceAccess.Unlock() + runtime.GC() + return nil +} + +func (s *StartedService) SetError(err error) { + s.serviceAccess.Lock() + s.updateStatusError(err) + s.WriteMessage(log.LevelError, err.Error()) +} + +func (s *StartedService) StopService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + err := s.handler.ServiceStop() + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) ReloadService(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + err := s.handler.ServiceReload() + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SubscribeServiceStatus(empty *emptypb.Empty, server grpc.ServerStreamingServer[ServiceStatus]) error { + subscription, done, err := s.serviceStatusObserver.Subscribe() + if err != nil { + return err + } + defer s.serviceStatusObserver.UnSubscribe(subscription) + err = server.Send(s.serviceStatus) + if err != nil { + return err + } + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case newStatus := <-subscription: + err = server.Send(newStatus) + if err != nil { + return err + } + case <-done: + return nil + } + } +} + +func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerStreamingServer[Log]) error { + var savedLines []*log.Entry + s.logAccess.Lock() + savedLines = make([]*log.Entry, 0, s.logLines.Len()) + for element := s.logLines.Front(); element != nil; element = element.Next() { + savedLines = append(savedLines, element.Value) + } + s.logAccess.Unlock() + subscription, done, err := s.logObserver.Subscribe() + if err != nil { + return err + } + defer s.logObserver.UnSubscribe(subscription) + err = server.Send(&Log{ + Messages: common.Map(savedLines, func(it *log.Entry) *Log_Message { + return &Log_Message{ + Level: LogLevel(it.Level), + Message: it.Message, + } + }), + Reset_: true, + }) + if err != nil { + return err + } + for { + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case message := <-subscription: + var rawMessage Log + if message == nil { + rawMessage.Reset_ = true + } else { + rawMessage.Messages = append(rawMessage.Messages, &Log_Message{ + Level: LogLevel(message.Level), + Message: message.Message, + }) + } + fetch: + for { + select { + case message = <-subscription: + if message == nil { + rawMessage.Messages = nil + rawMessage.Reset_ = true + } else { + rawMessage.Messages = append(rawMessage.Messages, &Log_Message{ + Level: LogLevel(message.Level), + Message: message.Message, + }) + } + default: + break fetch + } + } + err = server.Send(&rawMessage) + if err != nil { + return err + } + case <-done: + return nil + } + } +} + +func (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb.Empty) (*DefaultLogLevel, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + logLevel := s.instance.instance.LogFactory().Level() + s.serviceAccess.RUnlock() + return &DefaultLogLevel{Level: LogLevel(logLevel)}, nil +} + +func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error { + interval := time.Duration(request.Interval) + if interval <= 0 { + interval = time.Second // Default to 1 second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + status := s.readStatus() + uploadTotal := status.UplinkTotal + downloadTotal := status.DownlinkTotal + for { + err := server.Send(status) + if err != nil { + return err + } + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-ticker.C: + } + status = s.readStatus() + upload := status.UplinkTotal - uploadTotal + download := status.DownlinkTotal - downloadTotal + uploadTotal = status.UplinkTotal + downloadTotal = status.DownlinkTotal + status.Uplink = upload + status.Downlink = download + } +} + +func (s *StartedService) readStatus() *Status { + var status Status + status.Memory = memory.Inuse() + status.Goroutines = int32(runtime.NumGoroutine()) + status.ConnectionsOut = int32(conntrack.Count()) + s.serviceAccess.RLock() + nowService := s.instance + s.serviceAccess.RUnlock() + if nowService != nil { + if clashServer := nowService.clashServer; clashServer != nil { + status.TrafficAvailable = true + trafficManager := clashServer.(*clashapi.Server).TrafficManager() + status.UplinkTotal, status.DownlinkTotal = trafficManager.Total() + status.ConnectionsIn = int32(trafficManager.ConnectionsLen()) + } + } + return &status +} + +func (s *StartedService) SubscribeGroups(empty *emptypb.Empty, server grpc.ServerStreamingServer[Groups]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + subscription, done, err := s.urlTestObserver.Subscribe() + if err != nil { + return err + } + defer s.urlTestObserver.UnSubscribe(subscription) + for { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return os.ErrInvalid + } + groups := s.readGroups() + s.serviceAccess.RUnlock() + err = server.Send(groups) + if err != nil { + return err + } + select { + case <-subscription: + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + } + } +} + +func (s *StartedService) readGroups() *Groups { + historyStorage := s.instance.urlTestHistoryStorage + boxService := s.instance + outbounds := boxService.instance.Outbound().Outbounds() + var iGroups []adapter.OutboundGroup + for _, it := range outbounds { + if group, isGroup := it.(adapter.OutboundGroup); isGroup { + iGroups = append(iGroups, group) + } + } + var gs Groups + for _, iGroup := range iGroups { + var g Group + g.Tag = iGroup.Tag() + g.Type = iGroup.Type() + _, g.Selectable = iGroup.(*group.Selector) + g.Selected = iGroup.Now() + if boxService.cacheFile != nil { + if isExpand, loaded := boxService.cacheFile.LoadGroupExpand(g.Tag); loaded { + g.IsExpand = isExpand + } + } + + for _, itemTag := range iGroup.All() { + itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag) + if !isLoaded { + continue + } + + var item GroupItem + item.Tag = itemTag + item.Type = itemOutbound.Type() + if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil { + item.UrlTestTime = history.Time.Unix() + item.UrlTestDelay = int32(history.Delay) + } + g.Items = append(g.Items, &item) + } + if len(g.Items) < 2 { + continue + } + gs.Group = append(gs.Group, &g) + } + return &gs +} + +func (s *StartedService) GetClashModeStatus(ctx context.Context, empty *emptypb.Empty) (*ClashModeStatus, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + clashServer := s.instance.clashServer + s.serviceAccess.RUnlock() + if clashServer == nil { + return nil, os.ErrInvalid + } + return &ClashModeStatus{ + ModeList: clashServer.ModeList(), + CurrentMode: clashServer.Mode(), + }, nil +} + +func (s *StartedService) SubscribeClashMode(empty *emptypb.Empty, server grpc.ServerStreamingServer[ClashMode]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + subscription, done, err := s.clashModeObserver.Subscribe() + if err != nil { + return err + } + defer s.clashModeObserver.UnSubscribe(subscription) + for { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return os.ErrInvalid + } + message := &ClashMode{Mode: s.instance.clashServer.Mode()} + s.serviceAccess.RUnlock() + err = server.Send(message) + if err != nil { + return err + } + select { + case <-subscription: + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-done: + return nil + } + } +} + +func (s *StartedService) SetClashMode(ctx context.Context, request *ClashMode) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + clashServer := s.instance.clashServer + s.serviceAccess.RUnlock() + clashServer.(*clashapi.Server).SetMode(request.Mode) + return &emptypb.Empty{}, nil +} + +func (s *StartedService) URLTest(ctx context.Context, request *URLTestRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + groupTag := request.OutboundTag + abstractOutboundGroup, isLoaded := boxService.instance.Outbound().Outbound(groupTag) + if !isLoaded { + return nil, E.New("outbound group not found: ", groupTag) + } + outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup) + if !isOutboundGroup { + return nil, E.New("outbound is not a group: ", groupTag) + } + urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) + if isURLTest { + go urlTest.CheckOutbounds() + } else { + historyStorage := boxService.urlTestHistoryStorage + + outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { + itOutbound, _ := boxService.instance.Outbound().Outbound(it) + return itOutbound + }), func(it adapter.Outbound) bool { + if it == nil { + return false + } + _, isGroup := it.(adapter.OutboundGroup) + if isGroup { + return false + } + return true + }) + b, _ := batch.New(boxService.ctx, batch.WithConcurrencyNum[any](10)) + for _, detour := range outbounds { + outboundToTest := detour + outboundTag := outboundToTest.Tag() + b.Go(outboundTag, func() (any, error) { + t, err := urltest.URLTest(boxService.ctx, "", outboundToTest) + if err != nil { + historyStorage.DeleteURLTestHistory(outboundTag) + } else { + historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{ + Time: time.Now(), + Delay: t, + }) + } + return nil, nil + }) + } + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SelectOutbound(ctx context.Context, request *SelectOutboundRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance.instance + s.serviceAccess.RUnlock() + outboundGroup, isLoaded := boxService.Outbound().Outbound(request.GroupTag) + if !isLoaded { + return nil, E.New("selector not found: ", request.GroupTag) + } + selector, isSelector := outboundGroup.(*group.Selector) + if !isSelector { + return nil, E.New("outbound is not a selector: ", request.GroupTag) + } + if !selector.SelectOutbound(request.OutboundTag) { + return nil, E.New("outbound not found in selector: ", request.OutboundTag) + } + s.urlTestObserver.Emit(struct{}{}) + return &emptypb.Empty{}, nil +} + +func (s *StartedService) SetGroupExpand(ctx context.Context, request *SetGroupExpandRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + if boxService.cacheFile != nil { + err := boxService.cacheFile.StoreGroupExpand(request.GroupTag, request.IsExpand) + if err != nil { + return nil, err + } + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) GetSystemProxyStatus(ctx context.Context, empty *emptypb.Empty) (*SystemProxyStatus, error) { + return s.handler.SystemProxyStatus() +} + +func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) { + err := s.handler.SetSystemProxyEnabled(request.Enabled) + if err != nil { + return nil, err + } + return nil, err +} + +func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error { + err := s.waitForStarted(server.Context()) + if err != nil { + return err + } + s.serviceAccess.RLock() + boxService := s.instance + s.serviceAccess.RUnlock() + ticker := time.NewTicker(time.Duration(request.Interval)) + defer ticker.Stop() + trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager() + var ( + connections = make(map[uuid.UUID]*Connection) + outConnections []*Connection + ) + for { + outConnections = outConnections[:0] + for _, connection := range trafficManager.Connections() { + outConnections = append(outConnections, newConnection(connections, connection, false)) + } + for _, connection := range trafficManager.ClosedConnections() { + outConnections = append(outConnections, newConnection(connections, connection, true)) + } + err := server.Send(&Connections{Connections: outConnections}) + if err != nil { + return err + } + select { + case <-s.ctx.Done(): + return s.ctx.Err() + case <-server.Context().Done(): + return server.Context().Err() + case <-ticker.C: + } + } +} + +func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) *Connection { + if oldConnection, loaded := connections[metadata.ID]; loaded { + if isClosed { + if oldConnection.ClosedAt == 0 { + oldConnection.Uplink = 0 + oldConnection.Downlink = 0 + oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli() + } + return oldConnection + } + lastUplink := oldConnection.UplinkTotal + lastDownlink := oldConnection.DownlinkTotal + uplinkTotal := metadata.Upload.Load() + downlinkTotal := metadata.Download.Load() + oldConnection.Uplink = uplinkTotal - lastUplink + oldConnection.Downlink = downlinkTotal - lastDownlink + oldConnection.UplinkTotal = uplinkTotal + oldConnection.DownlinkTotal = downlinkTotal + return oldConnection + } + var rule string + if metadata.Rule != nil { + rule = metadata.Rule.String() + } + uplinkTotal := metadata.Upload.Load() + downlinkTotal := metadata.Download.Load() + uplink := uplinkTotal + downlink := downlinkTotal + var closedAt int64 + if !metadata.ClosedAt.IsZero() { + closedAt = metadata.ClosedAt.UnixMilli() + uplink = 0 + downlink = 0 + } + connection := &Connection{ + Id: metadata.ID.String(), + Inbound: metadata.Metadata.Inbound, + InboundType: metadata.Metadata.InboundType, + IpVersion: int32(metadata.Metadata.IPVersion), + Network: metadata.Metadata.Network, + Source: metadata.Metadata.Source.String(), + Destination: metadata.Metadata.Destination.String(), + Domain: metadata.Metadata.Domain, + Protocol: metadata.Metadata.Protocol, + User: metadata.Metadata.User, + FromOutbound: metadata.Metadata.Outbound, + CreatedAt: metadata.CreatedAt.UnixMilli(), + ClosedAt: closedAt, + Uplink: uplink, + Downlink: downlink, + UplinkTotal: uplinkTotal, + DownlinkTotal: downlinkTotal, + Rule: rule, + Outbound: metadata.Outbound, + OutboundType: metadata.OutboundType, + ChainList: metadata.Chain, + } + connections[metadata.ID] = connection + return connection +} + +func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) { + s.serviceAccess.RLock() + switch s.serviceStatus.Status { + case ServiceStatus_STARTING, ServiceStatus_STARTED: + default: + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + targetConn := boxService.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(request.Id)) + if targetConn != nil { + targetConn.Close() + } + return &emptypb.Empty{}, nil +} + +func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + conntrack.Close() + return &emptypb.Empty{}, nil +} + +func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *emptypb.Empty) (*DeprecatedWarnings, error) { + s.serviceAccess.RLock() + if s.serviceStatus.Status != ServiceStatus_STARTED { + s.serviceAccess.RUnlock() + return nil, os.ErrInvalid + } + boxService := s.instance + s.serviceAccess.RUnlock() + notes := service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get() + return &DeprecatedWarnings{ + Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning { + return &DeprecatedWarning{ + Message: it.Message(), + Impending: it.Impending(), + MigrationLink: it.MigrationLink, + } + }), + }, nil +} + +func (s *StartedService) SubscribeHelperEvents(empty *emptypb.Empty, server grpc.ServerStreamingServer[HelperRequest]) error { + return os.ErrInvalid +} + +func (s *StartedService) SendHelperResponse(ctx context.Context, response *HelperResponse) (*emptypb.Empty, error) { + return nil, os.ErrInvalid +} + +func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() { +} + +func (s *StartedService) WriteMessage(level log.Level, message string) { + item := &log.Entry{Level: level, Message: message} + s.logSubscriber.Emit(item) + s.logAccess.Lock() + s.logLines.PushBack(item) + if s.logLines.Len() > s.logMaxLines { + s.logLines.Remove(s.logLines.Front()) + } + s.logAccess.Unlock() + if s.debug { + s.handler.WriteDebugMessage(message) + } +} + +func (s *StartedService) Instance() *Instance { + s.serviceAccess.RLock() + defer s.serviceAccess.RUnlock() + return s.instance +} diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go new file mode 100644 index 00000000..90cff998 --- /dev/null +++ b/daemon/started_service.pb.go @@ -0,0 +1,1906 @@ +package daemon + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LogLevel int32 + +const ( + LogLevel_PANIC LogLevel = 0 + LogLevel_FATAL LogLevel = 1 + LogLevel_ERROR LogLevel = 2 + LogLevel_WARN LogLevel = 3 + LogLevel_INFO LogLevel = 4 + LogLevel_DEBUG LogLevel = 5 + LogLevel_TRACE LogLevel = 6 +) + +// Enum value maps for LogLevel. +var ( + LogLevel_name = map[int32]string{ + 0: "PANIC", + 1: "FATAL", + 2: "ERROR", + 3: "WARN", + 4: "INFO", + 5: "DEBUG", + 6: "TRACE", + } + LogLevel_value = map[string]int32{ + "PANIC": 0, + "FATAL": 1, + "ERROR": 2, + "WARN": 3, + "INFO": 4, + "DEBUG": 5, + "TRACE": 6, + } +) + +func (x LogLevel) Enum() *LogLevel { + p := new(LogLevel) + *p = x + return p +} + +func (x LogLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LogLevel) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[0].Descriptor() +} + +func (LogLevel) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[0] +} + +func (x LogLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LogLevel.Descriptor instead. +func (LogLevel) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0} +} + +type ConnectionFilter int32 + +const ( + ConnectionFilter_ALL ConnectionFilter = 0 + ConnectionFilter_ACTIVE ConnectionFilter = 1 + ConnectionFilter_CLOSED ConnectionFilter = 2 +) + +// Enum value maps for ConnectionFilter. +var ( + ConnectionFilter_name = map[int32]string{ + 0: "ALL", + 1: "ACTIVE", + 2: "CLOSED", + } + ConnectionFilter_value = map[string]int32{ + "ALL": 0, + "ACTIVE": 1, + "CLOSED": 2, + } +) + +func (x ConnectionFilter) Enum() *ConnectionFilter { + p := new(ConnectionFilter) + *p = x + return p +} + +func (x ConnectionFilter) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectionFilter) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[1].Descriptor() +} + +func (ConnectionFilter) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[1] +} + +func (x ConnectionFilter) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectionFilter.Descriptor instead. +func (ConnectionFilter) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{1} +} + +type ConnectionSortBy int32 + +const ( + ConnectionSortBy_DATE ConnectionSortBy = 0 + ConnectionSortBy_TRAFFIC ConnectionSortBy = 1 + ConnectionSortBy_TOTAL_TRAFFIC ConnectionSortBy = 2 +) + +// Enum value maps for ConnectionSortBy. +var ( + ConnectionSortBy_name = map[int32]string{ + 0: "DATE", + 1: "TRAFFIC", + 2: "TOTAL_TRAFFIC", + } + ConnectionSortBy_value = map[string]int32{ + "DATE": 0, + "TRAFFIC": 1, + "TOTAL_TRAFFIC": 2, + } +) + +func (x ConnectionSortBy) Enum() *ConnectionSortBy { + p := new(ConnectionSortBy) + *p = x + return p +} + +func (x ConnectionSortBy) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectionSortBy) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[2].Descriptor() +} + +func (ConnectionSortBy) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[2] +} + +func (x ConnectionSortBy) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectionSortBy.Descriptor instead. +func (ConnectionSortBy) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{2} +} + +type ServiceStatus_Type int32 + +const ( + ServiceStatus_IDLE ServiceStatus_Type = 0 + ServiceStatus_STARTING ServiceStatus_Type = 1 + ServiceStatus_STARTED ServiceStatus_Type = 2 + ServiceStatus_STOPPING ServiceStatus_Type = 3 + ServiceStatus_FATAL ServiceStatus_Type = 4 +) + +// Enum value maps for ServiceStatus_Type. +var ( + ServiceStatus_Type_name = map[int32]string{ + 0: "IDLE", + 1: "STARTING", + 2: "STARTED", + 3: "STOPPING", + 4: "FATAL", + } + ServiceStatus_Type_value = map[string]int32{ + "IDLE": 0, + "STARTING": 1, + "STARTED": 2, + "STOPPING": 3, + "FATAL": 4, + } +) + +func (x ServiceStatus_Type) Enum() *ServiceStatus_Type { + p := new(ServiceStatus_Type) + *p = x + return p +} + +func (x ServiceStatus_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ServiceStatus_Type) Descriptor() protoreflect.EnumDescriptor { + return file_daemon_started_service_proto_enumTypes[3].Descriptor() +} + +func (ServiceStatus_Type) Type() protoreflect.EnumType { + return &file_daemon_started_service_proto_enumTypes[3] +} + +func (x ServiceStatus_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ServiceStatus_Type.Descriptor instead. +func (ServiceStatus_Type) EnumDescriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0, 0} +} + +type ServiceStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status ServiceStatus_Type `protobuf:"varint,1,opt,name=status,proto3,enum=daemon.ServiceStatus_Type" json:"status,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=errorMessage,proto3" json:"errorMessage,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServiceStatus) Reset() { + *x = ServiceStatus{} + mi := &file_daemon_started_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServiceStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceStatus) ProtoMessage() {} + +func (x *ServiceStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServiceStatus.ProtoReflect.Descriptor instead. +func (*ServiceStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ServiceStatus) GetStatus() ServiceStatus_Type { + if x != nil { + return x.Status + } + return ServiceStatus_IDLE +} + +func (x *ServiceStatus) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +type ReloadServiceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + NewProfileContent string `protobuf:"bytes,1,opt,name=newProfileContent,proto3" json:"newProfileContent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReloadServiceRequest) Reset() { + *x = ReloadServiceRequest{} + mi := &file_daemon_started_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReloadServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReloadServiceRequest) ProtoMessage() {} + +func (x *ReloadServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReloadServiceRequest.ProtoReflect.Descriptor instead. +func (*ReloadServiceRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ReloadServiceRequest) GetNewProfileContent() string { + if x != nil { + return x.NewProfileContent + } + return "" +} + +type SubscribeStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeStatusRequest) Reset() { + *x = SubscribeStatusRequest{} + mi := &file_daemon_started_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeStatusRequest) ProtoMessage() {} + +func (x *SubscribeStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeStatusRequest.ProtoReflect.Descriptor instead. +func (*SubscribeStatusRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{2} +} + +func (x *SubscribeStatusRequest) GetInterval() int64 { + if x != nil { + return x.Interval + } + return 0 +} + +type Log struct { + state protoimpl.MessageState `protogen:"open.v1"` + Messages []*Log_Message `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Log) Reset() { + *x = Log{} + mi := &file_daemon_started_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Log) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log) ProtoMessage() {} + +func (x *Log) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Log.ProtoReflect.Descriptor instead. +func (*Log) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{3} +} + +func (x *Log) GetMessages() []*Log_Message { + if x != nil { + return x.Messages + } + return nil +} + +func (x *Log) GetReset_() bool { + if x != nil { + return x.Reset_ + } + return false +} + +type DefaultLogLevel struct { + state protoimpl.MessageState `protogen:"open.v1"` + Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DefaultLogLevel) Reset() { + *x = DefaultLogLevel{} + mi := &file_daemon_started_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DefaultLogLevel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DefaultLogLevel) ProtoMessage() {} + +func (x *DefaultLogLevel) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DefaultLogLevel.ProtoReflect.Descriptor instead. +func (*DefaultLogLevel) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{4} +} + +func (x *DefaultLogLevel) GetLevel() LogLevel { + if x != nil { + return x.Level + } + return LogLevel_PANIC +} + +type Status struct { + state protoimpl.MessageState `protogen:"open.v1"` + Memory uint64 `protobuf:"varint,1,opt,name=memory,proto3" json:"memory,omitempty"` + Goroutines int32 `protobuf:"varint,2,opt,name=goroutines,proto3" json:"goroutines,omitempty"` + ConnectionsIn int32 `protobuf:"varint,3,opt,name=connectionsIn,proto3" json:"connectionsIn,omitempty"` + ConnectionsOut int32 `protobuf:"varint,4,opt,name=connectionsOut,proto3" json:"connectionsOut,omitempty"` + TrafficAvailable bool `protobuf:"varint,5,opt,name=trafficAvailable,proto3" json:"trafficAvailable,omitempty"` + Uplink int64 `protobuf:"varint,6,opt,name=uplink,proto3" json:"uplink,omitempty"` + Downlink int64 `protobuf:"varint,7,opt,name=downlink,proto3" json:"downlink,omitempty"` + UplinkTotal int64 `protobuf:"varint,8,opt,name=uplinkTotal,proto3" json:"uplinkTotal,omitempty"` + DownlinkTotal int64 `protobuf:"varint,9,opt,name=downlinkTotal,proto3" json:"downlinkTotal,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Status) Reset() { + *x = Status{} + mi := &file_daemon_started_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Status.ProtoReflect.Descriptor instead. +func (*Status) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{5} +} + +func (x *Status) GetMemory() uint64 { + if x != nil { + return x.Memory + } + return 0 +} + +func (x *Status) GetGoroutines() int32 { + if x != nil { + return x.Goroutines + } + return 0 +} + +func (x *Status) GetConnectionsIn() int32 { + if x != nil { + return x.ConnectionsIn + } + return 0 +} + +func (x *Status) GetConnectionsOut() int32 { + if x != nil { + return x.ConnectionsOut + } + return 0 +} + +func (x *Status) GetTrafficAvailable() bool { + if x != nil { + return x.TrafficAvailable + } + return false +} + +func (x *Status) GetUplink() int64 { + if x != nil { + return x.Uplink + } + return 0 +} + +func (x *Status) GetDownlink() int64 { + if x != nil { + return x.Downlink + } + return 0 +} + +func (x *Status) GetUplinkTotal() int64 { + if x != nil { + return x.UplinkTotal + } + return 0 +} + +func (x *Status) GetDownlinkTotal() int64 { + if x != nil { + return x.DownlinkTotal + } + return 0 +} + +type Groups struct { + state protoimpl.MessageState `protogen:"open.v1"` + Group []*Group `protobuf:"bytes,1,rep,name=group,proto3" json:"group,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Groups) Reset() { + *x = Groups{} + mi := &file_daemon_started_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Groups) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Groups) ProtoMessage() {} + +func (x *Groups) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Groups.ProtoReflect.Descriptor instead. +func (*Groups) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{6} +} + +func (x *Groups) GetGroup() []*Group { + if x != nil { + return x.Group + } + return nil +} + +type Group struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Selectable bool `protobuf:"varint,3,opt,name=selectable,proto3" json:"selectable,omitempty"` + Selected string `protobuf:"bytes,4,opt,name=selected,proto3" json:"selected,omitempty"` + IsExpand bool `protobuf:"varint,5,opt,name=isExpand,proto3" json:"isExpand,omitempty"` + Items []*GroupItem `protobuf:"bytes,6,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Group) Reset() { + *x = Group{} + mi := &file_daemon_started_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Group) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Group) ProtoMessage() {} + +func (x *Group) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Group.ProtoReflect.Descriptor instead. +func (*Group) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{7} +} + +func (x *Group) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *Group) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Group) GetSelectable() bool { + if x != nil { + return x.Selectable + } + return false +} + +func (x *Group) GetSelected() string { + if x != nil { + return x.Selected + } + return "" +} + +func (x *Group) GetIsExpand() bool { + if x != nil { + return x.IsExpand + } + return false +} + +func (x *Group) GetItems() []*GroupItem { + if x != nil { + return x.Items + } + return nil +} + +type GroupItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + UrlTestTime int64 `protobuf:"varint,3,opt,name=urlTestTime,proto3" json:"urlTestTime,omitempty"` + UrlTestDelay int32 `protobuf:"varint,4,opt,name=urlTestDelay,proto3" json:"urlTestDelay,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GroupItem) Reset() { + *x = GroupItem{} + mi := &file_daemon_started_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GroupItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GroupItem) ProtoMessage() {} + +func (x *GroupItem) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GroupItem.ProtoReflect.Descriptor instead. +func (*GroupItem) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{8} +} + +func (x *GroupItem) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *GroupItem) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *GroupItem) GetUrlTestTime() int64 { + if x != nil { + return x.UrlTestTime + } + return 0 +} + +func (x *GroupItem) GetUrlTestDelay() int32 { + if x != nil { + return x.UrlTestDelay + } + return 0 +} + +type URLTestRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OutboundTag string `protobuf:"bytes,1,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *URLTestRequest) Reset() { + *x = URLTestRequest{} + mi := &file_daemon_started_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *URLTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*URLTestRequest) ProtoMessage() {} + +func (x *URLTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use URLTestRequest.ProtoReflect.Descriptor instead. +func (*URLTestRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{9} +} + +func (x *URLTestRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +type SelectOutboundRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + GroupTag string `protobuf:"bytes,1,opt,name=groupTag,proto3" json:"groupTag,omitempty"` + OutboundTag string `protobuf:"bytes,2,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SelectOutboundRequest) Reset() { + *x = SelectOutboundRequest{} + mi := &file_daemon_started_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SelectOutboundRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SelectOutboundRequest) ProtoMessage() {} + +func (x *SelectOutboundRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SelectOutboundRequest.ProtoReflect.Descriptor instead. +func (*SelectOutboundRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{10} +} + +func (x *SelectOutboundRequest) GetGroupTag() string { + if x != nil { + return x.GroupTag + } + return "" +} + +func (x *SelectOutboundRequest) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +type SetGroupExpandRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + GroupTag string `protobuf:"bytes,1,opt,name=groupTag,proto3" json:"groupTag,omitempty"` + IsExpand bool `protobuf:"varint,2,opt,name=isExpand,proto3" json:"isExpand,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetGroupExpandRequest) Reset() { + *x = SetGroupExpandRequest{} + mi := &file_daemon_started_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetGroupExpandRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetGroupExpandRequest) ProtoMessage() {} + +func (x *SetGroupExpandRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetGroupExpandRequest.ProtoReflect.Descriptor instead. +func (*SetGroupExpandRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{11} +} + +func (x *SetGroupExpandRequest) GetGroupTag() string { + if x != nil { + return x.GroupTag + } + return "" +} + +func (x *SetGroupExpandRequest) GetIsExpand() bool { + if x != nil { + return x.IsExpand + } + return false +} + +type ClashMode struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClashMode) Reset() { + *x = ClashMode{} + mi := &file_daemon_started_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClashMode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClashMode) ProtoMessage() {} + +func (x *ClashMode) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClashMode.ProtoReflect.Descriptor instead. +func (*ClashMode) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{12} +} + +func (x *ClashMode) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +type ClashModeStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModeList []string `protobuf:"bytes,1,rep,name=modeList,proto3" json:"modeList,omitempty"` + CurrentMode string `protobuf:"bytes,2,opt,name=currentMode,proto3" json:"currentMode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClashModeStatus) Reset() { + *x = ClashModeStatus{} + mi := &file_daemon_started_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClashModeStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClashModeStatus) ProtoMessage() {} + +func (x *ClashModeStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClashModeStatus.ProtoReflect.Descriptor instead. +func (*ClashModeStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{13} +} + +func (x *ClashModeStatus) GetModeList() []string { + if x != nil { + return x.ModeList + } + return nil +} + +func (x *ClashModeStatus) GetCurrentMode() string { + if x != nil { + return x.CurrentMode + } + return "" +} + +type SystemProxyStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + Available bool `protobuf:"varint,1,opt,name=available,proto3" json:"available,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SystemProxyStatus) Reset() { + *x = SystemProxyStatus{} + mi := &file_daemon_started_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SystemProxyStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemProxyStatus) ProtoMessage() {} + +func (x *SystemProxyStatus) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemProxyStatus.ProtoReflect.Descriptor instead. +func (*SystemProxyStatus) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{14} +} + +func (x *SystemProxyStatus) GetAvailable() bool { + if x != nil { + return x.Available + } + return false +} + +func (x *SystemProxyStatus) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type SetSystemProxyEnabledRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetSystemProxyEnabledRequest) Reset() { + *x = SetSystemProxyEnabledRequest{} + mi := &file_daemon_started_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetSystemProxyEnabledRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetSystemProxyEnabledRequest) ProtoMessage() {} + +func (x *SetSystemProxyEnabledRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetSystemProxyEnabledRequest.ProtoReflect.Descriptor instead. +func (*SetSystemProxyEnabledRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{15} +} + +func (x *SetSystemProxyEnabledRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type SubscribeConnectionsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"` + Filter ConnectionFilter `protobuf:"varint,2,opt,name=filter,proto3,enum=daemon.ConnectionFilter" json:"filter,omitempty"` + SortBy ConnectionSortBy `protobuf:"varint,3,opt,name=sortBy,proto3,enum=daemon.ConnectionSortBy" json:"sortBy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscribeConnectionsRequest) Reset() { + *x = SubscribeConnectionsRequest{} + mi := &file_daemon_started_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscribeConnectionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeConnectionsRequest) ProtoMessage() {} + +func (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeConnectionsRequest.ProtoReflect.Descriptor instead. +func (*SubscribeConnectionsRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{16} +} + +func (x *SubscribeConnectionsRequest) GetInterval() int64 { + if x != nil { + return x.Interval + } + return 0 +} + +func (x *SubscribeConnectionsRequest) GetFilter() ConnectionFilter { + if x != nil { + return x.Filter + } + return ConnectionFilter_ALL +} + +func (x *SubscribeConnectionsRequest) GetSortBy() ConnectionSortBy { + if x != nil { + return x.SortBy + } + return ConnectionSortBy_DATE +} + +type Connections struct { + state protoimpl.MessageState `protogen:"open.v1"` + Connections []*Connection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Connections) Reset() { + *x = Connections{} + mi := &file_daemon_started_service_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Connections) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Connections) ProtoMessage() {} + +func (x *Connections) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Connections.ProtoReflect.Descriptor instead. +func (*Connections) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{17} +} + +func (x *Connections) GetConnections() []*Connection { + if x != nil { + return x.Connections + } + return nil +} + +type Connection struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Inbound string `protobuf:"bytes,2,opt,name=inbound,proto3" json:"inbound,omitempty"` + InboundType string `protobuf:"bytes,3,opt,name=inboundType,proto3" json:"inboundType,omitempty"` + IpVersion int32 `protobuf:"varint,4,opt,name=ipVersion,proto3" json:"ipVersion,omitempty"` + Network string `protobuf:"bytes,5,opt,name=network,proto3" json:"network,omitempty"` + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + Destination string `protobuf:"bytes,7,opt,name=destination,proto3" json:"destination,omitempty"` + Domain string `protobuf:"bytes,8,opt,name=domain,proto3" json:"domain,omitempty"` + Protocol string `protobuf:"bytes,9,opt,name=protocol,proto3" json:"protocol,omitempty"` + User string `protobuf:"bytes,10,opt,name=user,proto3" json:"user,omitempty"` + FromOutbound string `protobuf:"bytes,11,opt,name=fromOutbound,proto3" json:"fromOutbound,omitempty"` + CreatedAt int64 `protobuf:"varint,12,opt,name=createdAt,proto3" json:"createdAt,omitempty"` + ClosedAt int64 `protobuf:"varint,13,opt,name=closedAt,proto3" json:"closedAt,omitempty"` + Uplink int64 `protobuf:"varint,14,opt,name=uplink,proto3" json:"uplink,omitempty"` + Downlink int64 `protobuf:"varint,15,opt,name=downlink,proto3" json:"downlink,omitempty"` + UplinkTotal int64 `protobuf:"varint,16,opt,name=uplinkTotal,proto3" json:"uplinkTotal,omitempty"` + DownlinkTotal int64 `protobuf:"varint,17,opt,name=downlinkTotal,proto3" json:"downlinkTotal,omitempty"` + Rule string `protobuf:"bytes,18,opt,name=rule,proto3" json:"rule,omitempty"` + Outbound string `protobuf:"bytes,19,opt,name=outbound,proto3" json:"outbound,omitempty"` + OutboundType string `protobuf:"bytes,20,opt,name=outboundType,proto3" json:"outboundType,omitempty"` + ChainList []string `protobuf:"bytes,21,rep,name=chainList,proto3" json:"chainList,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Connection) Reset() { + *x = Connection{} + mi := &file_daemon_started_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Connection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Connection) ProtoMessage() {} + +func (x *Connection) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Connection.ProtoReflect.Descriptor instead. +func (*Connection) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{18} +} + +func (x *Connection) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Connection) GetInbound() string { + if x != nil { + return x.Inbound + } + return "" +} + +func (x *Connection) GetInboundType() string { + if x != nil { + return x.InboundType + } + return "" +} + +func (x *Connection) GetIpVersion() int32 { + if x != nil { + return x.IpVersion + } + return 0 +} + +func (x *Connection) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Connection) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *Connection) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *Connection) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *Connection) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + +func (x *Connection) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *Connection) GetFromOutbound() string { + if x != nil { + return x.FromOutbound + } + return "" +} + +func (x *Connection) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *Connection) GetClosedAt() int64 { + if x != nil { + return x.ClosedAt + } + return 0 +} + +func (x *Connection) GetUplink() int64 { + if x != nil { + return x.Uplink + } + return 0 +} + +func (x *Connection) GetDownlink() int64 { + if x != nil { + return x.Downlink + } + return 0 +} + +func (x *Connection) GetUplinkTotal() int64 { + if x != nil { + return x.UplinkTotal + } + return 0 +} + +func (x *Connection) GetDownlinkTotal() int64 { + if x != nil { + return x.DownlinkTotal + } + return 0 +} + +func (x *Connection) GetRule() string { + if x != nil { + return x.Rule + } + return "" +} + +func (x *Connection) GetOutbound() string { + if x != nil { + return x.Outbound + } + return "" +} + +func (x *Connection) GetOutboundType() string { + if x != nil { + return x.OutboundType + } + return "" +} + +func (x *Connection) GetChainList() []string { + if x != nil { + return x.ChainList + } + return nil +} + +type CloseConnectionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CloseConnectionRequest) Reset() { + *x = CloseConnectionRequest{} + mi := &file_daemon_started_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CloseConnectionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseConnectionRequest) ProtoMessage() {} + +func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead. +func (*CloseConnectionRequest) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{19} +} + +func (x *CloseConnectionRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeprecatedWarnings struct { + state protoimpl.MessageState `protogen:"open.v1"` + Warnings []*DeprecatedWarning `protobuf:"bytes,1,rep,name=warnings,proto3" json:"warnings,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeprecatedWarnings) Reset() { + *x = DeprecatedWarnings{} + mi := &file_daemon_started_service_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprecatedWarnings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprecatedWarnings) ProtoMessage() {} + +func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead. +func (*DeprecatedWarnings) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{20} +} + +func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning { + if x != nil { + return x.Warnings + } + return nil +} + +type DeprecatedWarning struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Impending bool `protobuf:"varint,2,opt,name=impending,proto3" json:"impending,omitempty"` + MigrationLink string `protobuf:"bytes,3,opt,name=migrationLink,proto3" json:"migrationLink,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeprecatedWarning) Reset() { + *x = DeprecatedWarning{} + mi := &file_daemon_started_service_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeprecatedWarning) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeprecatedWarning) ProtoMessage() {} + +func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead. +func (*DeprecatedWarning) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{21} +} + +func (x *DeprecatedWarning) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *DeprecatedWarning) GetImpending() bool { + if x != nil { + return x.Impending + } + return false +} + +func (x *DeprecatedWarning) GetMigrationLink() string { + if x != nil { + return x.MigrationLink + } + return "" +} + +type Log_Message struct { + state protoimpl.MessageState `protogen:"open.v1"` + Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Log_Message) Reset() { + *x = Log_Message{} + mi := &file_daemon_started_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Log_Message) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log_Message) ProtoMessage() {} + +func (x *Log_Message) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Log_Message.ProtoReflect.Descriptor instead. +func (*Log_Message) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *Log_Message) GetLevel() LogLevel { + if x != nil { + return x.Level + } + return LogLevel_PANIC +} + +func (x *Log_Message) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_daemon_started_service_proto protoreflect.FileDescriptor + +const file_daemon_started_service_proto_rawDesc = "" + + "\n" + + "\x1cdaemon/started_service.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\x1a\x13daemon/helper.proto\"\xad\x01\n" + + "\rServiceStatus\x122\n" + + "\x06status\x18\x01 \x01(\x0e2\x1a.daemon.ServiceStatus.TypeR\x06status\x12\"\n" + + "\ferrorMessage\x18\x02 \x01(\tR\ferrorMessage\"D\n" + + "\x04Type\x12\b\n" + + "\x04IDLE\x10\x00\x12\f\n" + + "\bSTARTING\x10\x01\x12\v\n" + + "\aSTARTED\x10\x02\x12\f\n" + + "\bSTOPPING\x10\x03\x12\t\n" + + "\x05FATAL\x10\x04\"D\n" + + "\x14ReloadServiceRequest\x12,\n" + + "\x11newProfileContent\x18\x01 \x01(\tR\x11newProfileContent\"4\n" + + "\x16SubscribeStatusRequest\x12\x1a\n" + + "\binterval\x18\x01 \x01(\x03R\binterval\"\x99\x01\n" + + "\x03Log\x12/\n" + + "\bmessages\x18\x01 \x03(\v2\x13.daemon.Log.MessageR\bmessages\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\x1aK\n" + + "\aMessage\x12&\n" + + "\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"9\n" + + "\x0fDefaultLogLevel\x12&\n" + + "\x05level\x18\x01 \x01(\x0e2\x10.daemon.LogLevelR\x05level\"\xb6\x02\n" + + "\x06Status\x12\x16\n" + + "\x06memory\x18\x01 \x01(\x04R\x06memory\x12\x1e\n" + + "\n" + + "goroutines\x18\x02 \x01(\x05R\n" + + "goroutines\x12$\n" + + "\rconnectionsIn\x18\x03 \x01(\x05R\rconnectionsIn\x12&\n" + + "\x0econnectionsOut\x18\x04 \x01(\x05R\x0econnectionsOut\x12*\n" + + "\x10trafficAvailable\x18\x05 \x01(\bR\x10trafficAvailable\x12\x16\n" + + "\x06uplink\x18\x06 \x01(\x03R\x06uplink\x12\x1a\n" + + "\bdownlink\x18\a \x01(\x03R\bdownlink\x12 \n" + + "\vuplinkTotal\x18\b \x01(\x03R\vuplinkTotal\x12$\n" + + "\rdownlinkTotal\x18\t \x01(\x03R\rdownlinkTotal\"-\n" + + "\x06Groups\x12#\n" + + "\x05group\x18\x01 \x03(\v2\r.daemon.GroupR\x05group\"\xae\x01\n" + + "\x05Group\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12\x1e\n" + + "\n" + + "selectable\x18\x03 \x01(\bR\n" + + "selectable\x12\x1a\n" + + "\bselected\x18\x04 \x01(\tR\bselected\x12\x1a\n" + + "\bisExpand\x18\x05 \x01(\bR\bisExpand\x12'\n" + + "\x05items\x18\x06 \x03(\v2\x11.daemon.GroupItemR\x05items\"w\n" + + "\tGroupItem\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12 \n" + + "\vurlTestTime\x18\x03 \x01(\x03R\vurlTestTime\x12\"\n" + + "\furlTestDelay\x18\x04 \x01(\x05R\furlTestDelay\"2\n" + + "\x0eURLTestRequest\x12 \n" + + "\voutboundTag\x18\x01 \x01(\tR\voutboundTag\"U\n" + + "\x15SelectOutboundRequest\x12\x1a\n" + + "\bgroupTag\x18\x01 \x01(\tR\bgroupTag\x12 \n" + + "\voutboundTag\x18\x02 \x01(\tR\voutboundTag\"O\n" + + "\x15SetGroupExpandRequest\x12\x1a\n" + + "\bgroupTag\x18\x01 \x01(\tR\bgroupTag\x12\x1a\n" + + "\bisExpand\x18\x02 \x01(\bR\bisExpand\"\x1f\n" + + "\tClashMode\x12\x12\n" + + "\x04mode\x18\x03 \x01(\tR\x04mode\"O\n" + + "\x0fClashModeStatus\x12\x1a\n" + + "\bmodeList\x18\x01 \x03(\tR\bmodeList\x12 \n" + + "\vcurrentMode\x18\x02 \x01(\tR\vcurrentMode\"K\n" + + "\x11SystemProxyStatus\x12\x1c\n" + + "\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" + + "\x1cSetSystemProxyEnabledRequest\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\"\x9d\x01\n" + + "\x1bSubscribeConnectionsRequest\x12\x1a\n" + + "\binterval\x18\x01 \x01(\x03R\binterval\x120\n" + + "\x06filter\x18\x02 \x01(\x0e2\x18.daemon.ConnectionFilterR\x06filter\x120\n" + + "\x06sortBy\x18\x03 \x01(\x0e2\x18.daemon.ConnectionSortByR\x06sortBy\"C\n" + + "\vConnections\x124\n" + + "\vconnections\x18\x01 \x03(\v2\x12.daemon.ConnectionR\vconnections\"\xde\x04\n" + + "\n" + + "Connection\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\ainbound\x18\x02 \x01(\tR\ainbound\x12 \n" + + "\vinboundType\x18\x03 \x01(\tR\vinboundType\x12\x1c\n" + + "\tipVersion\x18\x04 \x01(\x05R\tipVersion\x12\x18\n" + + "\anetwork\x18\x05 \x01(\tR\anetwork\x12\x16\n" + + "\x06source\x18\x06 \x01(\tR\x06source\x12 \n" + + "\vdestination\x18\a \x01(\tR\vdestination\x12\x16\n" + + "\x06domain\x18\b \x01(\tR\x06domain\x12\x1a\n" + + "\bprotocol\x18\t \x01(\tR\bprotocol\x12\x12\n" + + "\x04user\x18\n" + + " \x01(\tR\x04user\x12\"\n" + + "\ffromOutbound\x18\v \x01(\tR\ffromOutbound\x12\x1c\n" + + "\tcreatedAt\x18\f \x01(\x03R\tcreatedAt\x12\x1a\n" + + "\bclosedAt\x18\r \x01(\x03R\bclosedAt\x12\x16\n" + + "\x06uplink\x18\x0e \x01(\x03R\x06uplink\x12\x1a\n" + + "\bdownlink\x18\x0f \x01(\x03R\bdownlink\x12 \n" + + "\vuplinkTotal\x18\x10 \x01(\x03R\vuplinkTotal\x12$\n" + + "\rdownlinkTotal\x18\x11 \x01(\x03R\rdownlinkTotal\x12\x12\n" + + "\x04rule\x18\x12 \x01(\tR\x04rule\x12\x1a\n" + + "\boutbound\x18\x13 \x01(\tR\boutbound\x12\"\n" + + "\foutboundType\x18\x14 \x01(\tR\foutboundType\x12\x1c\n" + + "\tchainList\x18\x15 \x03(\tR\tchainList\"(\n" + + "\x16CloseConnectionRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"K\n" + + "\x12DeprecatedWarnings\x125\n" + + "\bwarnings\x18\x01 \x03(\v2\x19.daemon.DeprecatedWarningR\bwarnings\"q\n" + + "\x11DeprecatedWarning\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\x12\x1c\n" + + "\timpending\x18\x02 \x01(\bR\timpending\x12$\n" + + "\rmigrationLink\x18\x03 \x01(\tR\rmigrationLink*U\n" + + "\bLogLevel\x12\t\n" + + "\x05PANIC\x10\x00\x12\t\n" + + "\x05FATAL\x10\x01\x12\t\n" + + "\x05ERROR\x10\x02\x12\b\n" + + "\x04WARN\x10\x03\x12\b\n" + + "\x04INFO\x10\x04\x12\t\n" + + "\x05DEBUG\x10\x05\x12\t\n" + + "\x05TRACE\x10\x06*3\n" + + "\x10ConnectionFilter\x12\a\n" + + "\x03ALL\x10\x00\x12\n" + + "\n" + + "\x06ACTIVE\x10\x01\x12\n" + + "\n" + + "\x06CLOSED\x10\x02*<\n" + + "\x10ConnectionSortBy\x12\b\n" + + "\x04DATE\x10\x00\x12\v\n" + + "\aTRAFFIC\x10\x01\x12\x11\n" + + "\rTOTAL_TRAFFIC\x10\x022\xf8\v\n" + + "\x0eStartedService\x12=\n" + + "\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" + + "\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" + + "\x16SubscribeServiceStatus\x12\x16.google.protobuf.Empty\x1a\x15.daemon.ServiceStatus\"\x000\x01\x127\n" + + "\fSubscribeLog\x12\x16.google.protobuf.Empty\x1a\v.daemon.Log\"\x000\x01\x12G\n" + + "\x12GetDefaultLogLevel\x12\x16.google.protobuf.Empty\x1a\x17.daemon.DefaultLogLevel\"\x00\x12E\n" + + "\x0fSubscribeStatus\x12\x1e.daemon.SubscribeStatusRequest\x1a\x0e.daemon.Status\"\x000\x01\x12=\n" + + "\x0fSubscribeGroups\x12\x16.google.protobuf.Empty\x1a\x0e.daemon.Groups\"\x000\x01\x12G\n" + + "\x12GetClashModeStatus\x12\x16.google.protobuf.Empty\x1a\x17.daemon.ClashModeStatus\"\x00\x12C\n" + + "\x12SubscribeClashMode\x12\x16.google.protobuf.Empty\x1a\x11.daemon.ClashMode\"\x000\x01\x12;\n" + + "\fSetClashMode\x12\x11.daemon.ClashMode\x1a\x16.google.protobuf.Empty\"\x00\x12;\n" + + "\aURLTest\x12\x16.daemon.URLTestRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" + + "\x0eSelectOutbound\x12\x1d.daemon.SelectOutboundRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" + + "\x0eSetGroupExpand\x12\x1d.daemon.SetGroupExpandRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" + + "\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" + + "\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12T\n" + + "\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x13.daemon.Connections\"\x000\x01\x12K\n" + + "\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" + + "\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" + + "\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12J\n" + + "\x15SubscribeHelperEvents\x12\x16.google.protobuf.Empty\x1a\x15.daemon.HelperRequest\"\x000\x01\x12F\n" + + "\x12SendHelperResponse\x12\x16.daemon.HelperResponse\x1a\x16.google.protobuf.Empty\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" + +var ( + file_daemon_started_service_proto_rawDescOnce sync.Once + file_daemon_started_service_proto_rawDescData []byte +) + +func file_daemon_started_service_proto_rawDescGZIP() []byte { + file_daemon_started_service_proto_rawDescOnce.Do(func() { + file_daemon_started_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc))) + }) + return file_daemon_started_service_proto_rawDescData +} + +var ( + file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) + file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 23) + file_daemon_started_service_proto_goTypes = []any{ + (LogLevel)(0), // 0: daemon.LogLevel + (ConnectionFilter)(0), // 1: daemon.ConnectionFilter + (ConnectionSortBy)(0), // 2: daemon.ConnectionSortBy + (ServiceStatus_Type)(0), // 3: daemon.ServiceStatus.Type + (*ServiceStatus)(nil), // 4: daemon.ServiceStatus + (*ReloadServiceRequest)(nil), // 5: daemon.ReloadServiceRequest + (*SubscribeStatusRequest)(nil), // 6: daemon.SubscribeStatusRequest + (*Log)(nil), // 7: daemon.Log + (*DefaultLogLevel)(nil), // 8: daemon.DefaultLogLevel + (*Status)(nil), // 9: daemon.Status + (*Groups)(nil), // 10: daemon.Groups + (*Group)(nil), // 11: daemon.Group + (*GroupItem)(nil), // 12: daemon.GroupItem + (*URLTestRequest)(nil), // 13: daemon.URLTestRequest + (*SelectOutboundRequest)(nil), // 14: daemon.SelectOutboundRequest + (*SetGroupExpandRequest)(nil), // 15: daemon.SetGroupExpandRequest + (*ClashMode)(nil), // 16: daemon.ClashMode + (*ClashModeStatus)(nil), // 17: daemon.ClashModeStatus + (*SystemProxyStatus)(nil), // 18: daemon.SystemProxyStatus + (*SetSystemProxyEnabledRequest)(nil), // 19: daemon.SetSystemProxyEnabledRequest + (*SubscribeConnectionsRequest)(nil), // 20: daemon.SubscribeConnectionsRequest + (*Connections)(nil), // 21: daemon.Connections + (*Connection)(nil), // 22: daemon.Connection + (*CloseConnectionRequest)(nil), // 23: daemon.CloseConnectionRequest + (*DeprecatedWarnings)(nil), // 24: daemon.DeprecatedWarnings + (*DeprecatedWarning)(nil), // 25: daemon.DeprecatedWarning + (*Log_Message)(nil), // 26: daemon.Log.Message + (*emptypb.Empty)(nil), // 27: google.protobuf.Empty + (*HelperResponse)(nil), // 28: daemon.HelperResponse + (*HelperRequest)(nil), // 29: daemon.HelperRequest + } +) + +var file_daemon_started_service_proto_depIdxs = []int32{ + 3, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type + 26, // 1: daemon.Log.messages:type_name -> daemon.Log.Message + 0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel + 11, // 3: daemon.Groups.group:type_name -> daemon.Group + 12, // 4: daemon.Group.items:type_name -> daemon.GroupItem + 1, // 5: daemon.SubscribeConnectionsRequest.filter:type_name -> daemon.ConnectionFilter + 2, // 6: daemon.SubscribeConnectionsRequest.sortBy:type_name -> daemon.ConnectionSortBy + 22, // 7: daemon.Connections.connections:type_name -> daemon.Connection + 25, // 8: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning + 0, // 9: daemon.Log.Message.level:type_name -> daemon.LogLevel + 27, // 10: daemon.StartedService.StopService:input_type -> google.protobuf.Empty + 27, // 11: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty + 27, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty + 27, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty + 27, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty + 6, // 15: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest + 27, // 16: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty + 27, // 17: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty + 27, // 18: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty + 16, // 19: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode + 13, // 20: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest + 14, // 21: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest + 15, // 22: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest + 27, // 23: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty + 19, // 24: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest + 20, // 25: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest + 23, // 26: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest + 27, // 27: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty + 27, // 28: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty + 27, // 29: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty + 28, // 30: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse + 27, // 31: daemon.StartedService.StopService:output_type -> google.protobuf.Empty + 27, // 32: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty + 4, // 33: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 7, // 34: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 8, // 35: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 9, // 36: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 10, // 37: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 17, // 38: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 16, // 39: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 27, // 40: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 27, // 41: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 27, // 42: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 27, // 43: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 18, // 44: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 27, // 45: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty + 21, // 46: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections + 27, // 47: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 27, // 48: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 24, // 49: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 29, // 50: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest + 27, // 51: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty + 31, // [31:52] is the sub-list for method output_type + 10, // [10:31] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_daemon_started_service_proto_init() } +func file_daemon_started_service_proto_init() { + if File_daemon_started_service_proto != nil { + return + } + file_daemon_helper_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)), + NumEnums: 4, + NumMessages: 23, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_daemon_started_service_proto_goTypes, + DependencyIndexes: file_daemon_started_service_proto_depIdxs, + EnumInfos: file_daemon_started_service_proto_enumTypes, + MessageInfos: file_daemon_started_service_proto_msgTypes, + }.Build() + File_daemon_started_service_proto = out.File + file_daemon_started_service_proto_goTypes = nil + file_daemon_started_service_proto_depIdxs = nil +} diff --git a/daemon/started_service.proto b/daemon/started_service.proto new file mode 100644 index 00000000..17e9f683 --- /dev/null +++ b/daemon/started_service.proto @@ -0,0 +1,204 @@ +syntax = "proto3"; + +package daemon; +option go_package = "github.com/sagernet/sing-box/daemon"; + +import "google/protobuf/empty.proto"; +import "daemon/helper.proto"; + +service StartedService { + rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {} + rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {} + rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {} + rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {} + rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {} + + rpc GetClashModeStatus(google.protobuf.Empty) returns(ClashModeStatus) {} + rpc SubscribeClashMode(google.protobuf.Empty) returns(stream ClashMode) {} + rpc SetClashMode(ClashMode) returns(google.protobuf.Empty) {} + + rpc URLTest(URLTestRequest) returns(google.protobuf.Empty) {} + rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {} + rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {} + + rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {} + rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {} + + rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream Connections) {} + rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {} + rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {} + rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {} + + rpc SubscribeHelperEvents(google.protobuf.Empty) returns(stream HelperRequest) {} + rpc SendHelperResponse(HelperResponse) returns(google.protobuf.Empty) {} +} + +message ServiceStatus { + enum Type { + IDLE = 0; + STARTING = 1; + STARTED = 2; + STOPPING = 3; + FATAL = 4; + } + Type status = 1; + string errorMessage = 2; +} + +message ReloadServiceRequest { + string newProfileContent = 1; +} + +message SubscribeStatusRequest { + int64 interval = 1; +} + +enum LogLevel { + PANIC = 0; + FATAL = 1; + ERROR = 2; + WARN = 3; + INFO = 4; + DEBUG = 5; + TRACE = 6; +} + +message Log { + repeated Message messages = 1; + bool reset = 2; + message Message { + LogLevel level = 1; + string message = 2; + } +} + +message DefaultLogLevel { + LogLevel level = 1; +} + +message Status { + uint64 memory = 1; + int32 goroutines = 2; + int32 connectionsIn = 3; + int32 connectionsOut = 4; + bool trafficAvailable = 5; + int64 uplink = 6; + int64 downlink = 7; + int64 uplinkTotal = 8; + int64 downlinkTotal = 9; +} + +message Groups { + repeated Group group = 1; +} + +message Group { + string tag = 1; + string type = 2; + bool selectable = 3; + string selected = 4; + bool isExpand = 5; + repeated GroupItem items = 6; +} + +message GroupItem { + string tag = 1; + string type = 2; + int64 urlTestTime = 3; + int32 urlTestDelay = 4; +} + +message URLTestRequest { + string outboundTag = 1; +} + +message SelectOutboundRequest { + string groupTag = 1; + string outboundTag = 2; +} + +message SetGroupExpandRequest { + string groupTag = 1; + bool isExpand = 2; +} + +message ClashMode { + string mode = 3; +} + +message ClashModeStatus { + repeated string modeList = 1; + string currentMode = 2; +} + +message SystemProxyStatus { + bool available = 1; + bool enabled = 2; +} + +message SetSystemProxyEnabledRequest { + bool enabled = 1; +} + +message SubscribeConnectionsRequest { + int64 interval = 1; + ConnectionFilter filter = 2; + ConnectionSortBy sortBy = 3; +} + +enum ConnectionFilter { + ALL = 0; + ACTIVE = 1; + CLOSED = 2; +} + +enum ConnectionSortBy { + DATE = 0; + TRAFFIC = 1; + TOTAL_TRAFFIC = 2; +} + +message Connections { + repeated Connection connections = 1; +} + +message Connection { + string id = 1; + string inbound = 2; + string inboundType = 3; + int32 ipVersion = 4; + string network = 5; + string source = 6; + string destination = 7; + string domain = 8; + string protocol = 9; + string user = 10; + string fromOutbound = 11; + int64 createdAt = 12; + int64 closedAt = 13; + int64 uplink = 14; + int64 downlink = 15; + int64 uplinkTotal = 16; + int64 downlinkTotal = 17; + string rule = 18; + string outbound = 19; + string outboundType = 20; + repeated string chainList = 21; +} + +message CloseConnectionRequest { + string id = 1; +} + +message DeprecatedWarnings { + repeated DeprecatedWarning warnings = 1; +} + +message DeprecatedWarning { + string message = 1; + bool impending = 2; + string migrationLink = 3; +} \ No newline at end of file diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go new file mode 100644 index 00000000..4807b25c --- /dev/null +++ b/daemon/started_service_grpc.pb.go @@ -0,0 +1,919 @@ +package daemon + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService" + StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService" + StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus" + StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog" + StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel" + StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus" + StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups" + StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus" + StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode" + StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode" + StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest" + StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound" + StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand" + StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus" + StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled" + StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections" + StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection" + StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections" + StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings" + StartedService_SubscribeHelperEvents_FullMethodName = "/daemon.StartedService/SubscribeHelperEvents" + StartedService_SendHelperResponse_FullMethodName = "/daemon.StartedService/SendHelperResponse" +) + +// StartedServiceClient is the client API for StartedService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StartedServiceClient interface { + StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) + SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) + GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) + SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) + SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) + GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) + SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) + SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) + URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) + SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) + CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) + SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) + SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type startedServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewStartedServiceClient(cc grpc.ClientConnInterface) StartedServiceClient { + return &startedServiceClient{cc} +} + +func (c *startedServiceClient) StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_StopService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_ReloadService_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[0], StartedService_SubscribeServiceStatus_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, ServiceStatus]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeServiceStatusClient = grpc.ServerStreamingClient[ServiceStatus] + +func (c *startedServiceClient) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[1], StartedService_SubscribeLog_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, Log]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeLogClient = grpc.ServerStreamingClient[Log] + +func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DefaultLogLevel) + err := c.cc.Invoke(ctx, StartedService_GetDefaultLogLevel_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SubscribeStatusRequest, Status]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeStatusClient = grpc.ServerStreamingClient[Status] + +func (c *startedServiceClient) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[3], StartedService_SubscribeGroups_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, Groups]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeGroupsClient = grpc.ServerStreamingClient[Groups] + +func (c *startedServiceClient) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClashModeStatus) + err := c.cc.Invoke(ctx, StartedService_GetClashModeStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[4], StartedService_SubscribeClashMode_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, ClashMode]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeClashModeClient = grpc.ServerStreamingClient[ClashMode] + +func (c *startedServiceClient) SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetClashMode_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_URLTest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SelectOutbound_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetGroupExpand_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SystemProxyStatus) + err := c.cc.Invoke(ctx, StartedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SubscribeConnectionsRequest, Connections]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[Connections] + +func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_CloseConnection_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_CloseAllConnections_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeprecatedWarnings) + err := c.cc.Invoke(ctx, StartedService_GetDeprecatedWarnings_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *startedServiceClient) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeHelperEvents_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, HelperRequest]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeHelperEventsClient = grpc.ServerStreamingClient[HelperRequest] + +func (c *startedServiceClient) SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_SendHelperResponse_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StartedServiceServer is the server API for StartedService service. +// All implementations must embed UnimplementedStartedServiceServer +// for forward compatibility. +type StartedServiceServer interface { + StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error + SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error + GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) + SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error + SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error + GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) + SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error + SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) + URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) + SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) + SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) + GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) + SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) + SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error + CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) + CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) + SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error + SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) + mustEmbedUnimplementedStartedServiceServer() +} + +// UnimplementedStartedServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedStartedServiceServer struct{} + +func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopService not implemented") +} + +func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReloadService not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeServiceStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeLog not implemented") +} + +func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeGroups not implemented") +} + +func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetClashModeStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeClashMode not implemented") +} + +func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetClashMode not implemented") +} + +func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method URLTest not implemented") +} + +func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SelectOutbound not implemented") +} + +func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetGroupExpand not implemented") +} + +func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSystemProxyStatus not implemented") +} + +func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetSystemProxyEnabled not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeConnections not implemented") +} + +func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CloseConnection not implemented") +} + +func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CloseAllConnections not implemented") +} + +func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented") +} + +func (UnimplementedStartedServiceServer) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error { + return status.Errorf(codes.Unimplemented, "method SubscribeHelperEvents not implemented") +} + +func (UnimplementedStartedServiceServer) SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendHelperResponse not implemented") +} +func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {} +func (UnimplementedStartedServiceServer) testEmbeddedByValue() {} + +// UnsafeStartedServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StartedServiceServer will +// result in compilation errors. +type UnsafeStartedServiceServer interface { + mustEmbedUnimplementedStartedServiceServer() +} + +func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) { + // If the following call pancis, it indicates UnimplementedStartedServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&StartedService_ServiceDesc, srv) +} + +func _StartedService_StopService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).StopService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_StopService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).StopService(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_ReloadService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).ReloadService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_ReloadService_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).ReloadService(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeServiceStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeServiceStatus(m, &grpc.GenericServerStream[emptypb.Empty, ServiceStatus]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeServiceStatusServer = grpc.ServerStreamingServer[ServiceStatus] + +func _StartedService_SubscribeLog_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeLog(m, &grpc.GenericServerStream[emptypb.Empty, Log]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeLogServer = grpc.ServerStreamingServer[Log] + +func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetDefaultLogLevel_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeStatusRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeStatus(m, &grpc.GenericServerStream[SubscribeStatusRequest, Status]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeStatusServer = grpc.ServerStreamingServer[Status] + +func _StartedService_SubscribeGroups_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeGroups(m, &grpc.GenericServerStream[emptypb.Empty, Groups]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeGroupsServer = grpc.ServerStreamingServer[Groups] + +func _StartedService_GetClashModeStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetClashModeStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetClashModeStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetClashModeStatus(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeClashMode_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeClashMode(m, &grpc.GenericServerStream[emptypb.Empty, ClashMode]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeClashModeServer = grpc.ServerStreamingServer[ClashMode] + +func _StartedService_SetClashMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClashMode) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetClashMode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetClashMode_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetClashMode(ctx, req.(*ClashMode)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_URLTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(URLTestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).URLTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_URLTest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).URLTest(ctx, req.(*URLTestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SelectOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SelectOutboundRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SelectOutbound(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SelectOutbound_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SelectOutbound(ctx, req.(*SelectOutboundRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetGroupExpandRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetGroupExpand(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetGroupExpand_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetGroupExpand(ctx, req.(*SetGroupExpandRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_GetSystemProxyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetSystemProxyStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetSystemProxyEnabledRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SetSystemProxyEnabled_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeConnectionsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, Connections]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[Connections] + +func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CloseConnectionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).CloseConnection(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_CloseConnection_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).CloseConnection(ctx, req.(*CloseConnectionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_CloseAllConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).CloseAllConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_CloseAllConnections_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).CloseAllConnections(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetDeprecatedWarnings_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _StartedService_SubscribeHelperEvents_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StartedServiceServer).SubscribeHelperEvents(m, &grpc.GenericServerStream[emptypb.Empty, HelperRequest]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type StartedService_SubscribeHelperEventsServer = grpc.ServerStreamingServer[HelperRequest] + +func _StartedService_SendHelperResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelperResponse) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).SendHelperResponse(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_SendHelperResponse_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).SendHelperResponse(ctx, req.(*HelperResponse)) + } + return interceptor(ctx, in, info, handler) +} + +// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var StartedService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "daemon.StartedService", + HandlerType: (*StartedServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "StopService", + Handler: _StartedService_StopService_Handler, + }, + { + MethodName: "ReloadService", + Handler: _StartedService_ReloadService_Handler, + }, + { + MethodName: "GetDefaultLogLevel", + Handler: _StartedService_GetDefaultLogLevel_Handler, + }, + { + MethodName: "GetClashModeStatus", + Handler: _StartedService_GetClashModeStatus_Handler, + }, + { + MethodName: "SetClashMode", + Handler: _StartedService_SetClashMode_Handler, + }, + { + MethodName: "URLTest", + Handler: _StartedService_URLTest_Handler, + }, + { + MethodName: "SelectOutbound", + Handler: _StartedService_SelectOutbound_Handler, + }, + { + MethodName: "SetGroupExpand", + Handler: _StartedService_SetGroupExpand_Handler, + }, + { + MethodName: "GetSystemProxyStatus", + Handler: _StartedService_GetSystemProxyStatus_Handler, + }, + { + MethodName: "SetSystemProxyEnabled", + Handler: _StartedService_SetSystemProxyEnabled_Handler, + }, + { + MethodName: "CloseConnection", + Handler: _StartedService_CloseConnection_Handler, + }, + { + MethodName: "CloseAllConnections", + Handler: _StartedService_CloseAllConnections_Handler, + }, + { + MethodName: "GetDeprecatedWarnings", + Handler: _StartedService_GetDeprecatedWarnings_Handler, + }, + { + MethodName: "SendHelperResponse", + Handler: _StartedService_SendHelperResponse_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "SubscribeServiceStatus", + Handler: _StartedService_SubscribeServiceStatus_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeLog", + Handler: _StartedService_SubscribeLog_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeStatus", + Handler: _StartedService_SubscribeStatus_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeGroups", + Handler: _StartedService_SubscribeGroups_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeClashMode", + Handler: _StartedService_SubscribeClashMode_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeConnections", + Handler: _StartedService_SubscribeConnections_Handler, + ServerStreams: true, + }, + { + StreamName: "SubscribeHelperEvents", + Handler: _StartedService_SubscribeHelperEvents_Handler, + ServerStreams: true, + }, + }, + Metadata: "daemon/started_service.proto", +} diff --git a/dns/router.go b/dns/router.go index 8de1f6a9..1038fdf0 100644 --- a/dns/router.go +++ b/dns/router.go @@ -10,7 +10,6 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" @@ -38,7 +37,7 @@ type Router struct { rules []adapter.DNSRule defaultDomainStrategy C.DomainStrategy dnsReverseMapping freelru.Cache[netip.Addr, string] - platformInterface platform.Interface + platformInterface adapter.PlatformInterface } func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router { diff --git a/docs/configuration/dns/server/index.zh.md b/docs/configuration/dns/server/index.zh.md index 64bdd88a..d6deef5a 100644 --- a/docs/configuration/dns/server/index.zh.md +++ b/docs/configuration/dns/server/index.zh.md @@ -1,48 +1,48 @@ ---- -icon: material/alert-decagram ---- - -!!! quote "sing-box 1.12.0 中的更改" - - :material-plus: [type](#type) - -# DNS Server - -### 结构 - -```json -{ - "dns": { - "servers": [ - { - "type": "", - "tag": "" - } - ] - } -} -``` - -#### type - -DNS 服务器的类型。 - -| 类型 | 格式 | -|-----------------|---------------------------| -| empty (default) | [Legacy](./legacy/) | -| `local` | [Local](./local/) | -| `hosts` | [Hosts](./hosts/) | -| `tcp` | [TCP](./tcp/) | -| `udp` | [UDP](./udp/) | -| `tls` | [TLS](./tls/) | -| `quic` | [QUIC](./quic/) | -| `https` | [HTTPS](./https/) | -| `h3` | [HTTP/3](./http3/) | -| `dhcp` | [DHCP](./dhcp/) | -| `fakeip` | [Fake IP](./fakeip/) | -| `tailscale` | [Tailscale](./tailscale/) | -| `resolved` | [Resolved](./resolved/) | - -#### tag - -DNS 服务器的标签。 +--- +icon: material/alert-decagram +--- + +!!! quote "sing-box 1.12.0 中的更改" + + :material-plus: [type](#type) + +# DNS Server + +### 结构 + +```json +{ + "dns": { + "servers": [ + { + "type": "", + "tag": "" + } + ] + } +} +``` + +#### type + +DNS 服务器的类型。 + +| 类型 | 格式 | +|-----------------|---------------------------| +| empty (default) | [Legacy](./legacy/) | +| `local` | [Local](./local/) | +| `hosts` | [Hosts](./hosts/) | +| `tcp` | [TCP](./tcp/) | +| `udp` | [UDP](./udp/) | +| `tls` | [TLS](./tls/) | +| `quic` | [QUIC](./quic/) | +| `https` | [HTTPS](./https/) | +| `h3` | [HTTP/3](./http3/) | +| `dhcp` | [DHCP](./dhcp/) | +| `fakeip` | [Fake IP](./fakeip/) | +| `tailscale` | [Tailscale](./tailscale/) | +| `resolved` | [Resolved](./resolved/) | + +#### tag + +DNS 服务器的标签。 diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 3a2d4827..e71031dc 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -24,6 +24,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" "github.com/sagernet/ws" @@ -53,7 +54,7 @@ type Server struct { mode string modeList []string - modeUpdateHook chan<- struct{} + modeUpdateHook *observable.Subscriber[struct{}] externalController bool externalUI string @@ -203,7 +204,7 @@ func (s *Server) ModeList() []string { return s.modeList } -func (s *Server) SetModeUpdateHook(hook chan<- struct{}) { +func (s *Server) SetModeUpdateHook(hook *observable.Subscriber[struct{}]) { s.modeUpdateHook = hook } @@ -221,10 +222,7 @@ func (s *Server) SetMode(newMode string) { } s.mode = newMode if s.modeUpdateHook != nil { - select { - case s.modeUpdateHook <- struct{}{}: - default: - } + s.modeUpdateHook.Emit(struct{}{}) } s.dnsRouter.ClearCache() cacheFile := service.FromContext[adapter.CacheFile](s.ctx) diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 48d54b25..9cddc8cc 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -45,15 +45,15 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) { if t.Metadata.ProcessInfo != nil { if t.Metadata.ProcessInfo.ProcessPath != "" { processPath = t.Metadata.ProcessInfo.ProcessPath - } else if t.Metadata.ProcessInfo.PackageName != "" { - processPath = t.Metadata.ProcessInfo.PackageName + } else if t.Metadata.ProcessInfo.AndroidPackageName != "" { + processPath = t.Metadata.ProcessInfo.AndroidPackageName } if processPath == "" { if t.Metadata.ProcessInfo.UserId != -1 { processPath = F.ToString(t.Metadata.ProcessInfo.UserId) } - } else if t.Metadata.ProcessInfo.User != "" { - processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.User, ")") + } else if t.Metadata.ProcessInfo.UserName != "" { + processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserName, ")") } else if t.Metadata.ProcessInfo.UserId != -1 { processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")") } diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go index 3eb57dd4..e3af6a19 100644 --- a/experimental/libbox/command.go +++ b/experimental/libbox/command.go @@ -3,18 +3,7 @@ package libbox const ( CommandLog int32 = iota CommandStatus - CommandServiceReload - CommandServiceClose - CommandCloseConnections CommandGroup - CommandSelectOutbound - CommandURLTest - CommandGroupExpand CommandClashMode - CommandSetClashMode - CommandGetSystemProxyStatus - CommandSetSystemProxyEnabled CommandConnections - CommandCloseConnection - CommandGetDeprecatedNotes ) diff --git a/experimental/libbox/command_clash_mode.go b/experimental/libbox/command_clash_mode.go deleted file mode 100644 index af69047f..00000000 --- a/experimental/libbox/command_clash_mode.go +++ /dev/null @@ -1,124 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "io" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/experimental/clashapi" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func (c *CommandClient) SetClashMode(newMode string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSetClashMode)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, newMode) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetClashMode(conn net.Conn) error { - newMode, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - service.clashServer.(*clashapi.Server).SetMode(newMode) - return writeError(conn, nil) -} - -func (c *CommandClient) handleModeConn(conn net.Conn) { - defer conn.Close() - - for { - newMode, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.UpdateClashMode(newMode) - } -} - -func (s *CommandServer) handleModeConn(conn net.Conn) error { - ctx := connKeepAlive(conn) - for s.service == nil { - select { - case <-time.After(time.Second): - continue - case <-ctx.Done(): - return ctx.Err() - } - } - err := writeClashModeList(conn, s.service.clashServer) - if err != nil { - return err - } - for { - select { - case <-s.modeUpdate: - err = varbin.Write(conn, binary.BigEndian, s.service.clashServer.Mode()) - if err != nil { - return err - } - case <-ctx.Done(): - return ctx.Err() - } - } -} - -func readClashModeList(reader io.Reader) (modeList []string, currentMode string, err error) { - var modeListLength uint16 - err = binary.Read(reader, binary.BigEndian, &modeListLength) - if err != nil { - return - } - if modeListLength == 0 { - return - } - modeList = make([]string, modeListLength) - for i := 0; i < int(modeListLength); i++ { - modeList[i], err = varbin.ReadValue[string](reader, binary.BigEndian) - if err != nil { - return - } - } - currentMode, err = varbin.ReadValue[string](reader, binary.BigEndian) - return -} - -func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error { - modeList := clashServer.ModeList() - err := binary.Write(writer, binary.BigEndian, uint16(len(modeList))) - if err != nil { - return err - } - if len(modeList) > 0 { - for _, mode := range modeList { - err = varbin.Write(writer, binary.BigEndian, mode) - if err != nil { - return err - } - } - err = varbin.Write(writer, binary.BigEndian, clashServer.Mode()) - if err != nil { - return err - } - } - return nil -} diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index fff2dbe2..f5f6c6e2 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -1,32 +1,49 @@ package libbox import ( - "encoding/binary" + "context" "net" "os" "path/filepath" + "strconv" + "sync" "time" + "github.com/sagernet/sing-box/daemon" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/types/known/emptypb" ) type CommandClient struct { - handler CommandClientHandler - conn net.Conn - options CommandClientOptions + handler CommandClientHandler + grpcConn *grpc.ClientConn + grpcClient daemon.StartedServiceClient + options CommandClientOptions + ctx context.Context + cancel context.CancelFunc + clientMutex sync.RWMutex } type CommandClientOptions struct { - Command int32 + commands []int32 StatusInterval int64 } +func (o *CommandClientOptions) AddCommand(command int32) { + o.commands = append(o.commands, command) +} + type CommandClientHandler interface { Connected() Disconnected(message string) + SetDefaultLogLevel(level int32) ClearLogs() - WriteLogs(messageList StringIterator) + WriteLogs(messageList LogIterator) WriteStatus(message *StatusMessage) WriteGroups(message OutboundGroupIterator) InitializeClashMode(modeList StringIterator, currentMode string) @@ -34,6 +51,17 @@ type CommandClientHandler interface { WriteConnections(message *Connections) } +type LogEntry struct { + Level int32 + Message string +} + +type LogIterator interface { + Len() int32 + HasNext() bool + Next() *LogEntry +} + func NewStandaloneCommandClient() *CommandClient { return new(CommandClient) } @@ -45,24 +73,38 @@ func NewCommandClient(handler CommandClientHandler, options *CommandClientOption } } -func (c *CommandClient) directConnect() (net.Conn, error) { - if !sTVOS { - return net.DialUnix("unix", nil, &net.UnixAddr{ - Name: filepath.Join(sBasePath, "command.sock"), - Net: "unix", - }) - } else { - return net.Dial("tcp", "127.0.0.1:8964") +func unaryClientAuthInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + if sCommandServerSecret != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret) } + return invoker(ctx, method, req, reply, cc, opts...) } -func (c *CommandClient) directConnectWithRetry() (net.Conn, error) { +func streamClientAuthInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + if sCommandServerSecret != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-command-secret", sCommandServerSecret) + } + return streamer(ctx, desc, cc, method, opts...) +} + +func (c *CommandClient) grpcDial() (*grpc.ClientConn, error) { + var target string + if sCommandServerListenPort == 0 { + target = "unix://" + filepath.Join(sBasePath, "command.sock") + } else { + target = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))) + } var ( - conn net.Conn + conn *grpc.ClientConn err error ) + clientOptions := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), + grpc.WithStreamInterceptor(streamClientAuthInterceptor), + } for i := 0; i < 10; i++ { - conn, err = c.directConnect() + conn, err = grpc.NewClient(target, clientOptions...) if err == nil { return conn, nil } @@ -72,79 +114,357 @@ func (c *CommandClient) directConnectWithRetry() (net.Conn, error) { } func (c *CommandClient) Connect() error { - common.Close(c.conn) - conn, err := c.directConnectWithRetry() + c.clientMutex.Lock() + common.Close(common.PtrOrNil(c.grpcConn)) + + conn, err := c.grpcDial() if err != nil { + c.clientMutex.Unlock() return err } - c.conn = conn - err = binary.Write(conn, binary.BigEndian, uint8(c.options.Command)) - if err != nil { - return err - } - switch c.options.Command { - case CommandLog: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") + c.grpcConn = conn + c.grpcClient = daemon.NewStartedServiceClient(conn) + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.clientMutex.Unlock() + + c.handler.Connected() + for _, command := range c.options.commands { + switch command { + case CommandLog: + go c.handleLogStream() + case CommandStatus: + go c.handleStatusStream() + case CommandGroup: + go c.handleGroupStream() + case CommandClashMode: + go c.handleClashModeStream() + case CommandConnections: + go c.handleConnectionsStream() + default: + return E.New("unknown command: ", command) } - c.handler.Connected() - go c.handleLogConn(conn) - case CommandStatus: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") - } - c.handler.Connected() - go c.handleStatusConn(conn) - case CommandGroup: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") - } - c.handler.Connected() - go c.handleGroupConn(conn) - case CommandClashMode: - var ( - modeList []string - currentMode string - ) - modeList, currentMode, err = readClashModeList(conn) - if err != nil { - return err - } - if sFixAndroidStack { - go func() { - c.handler.Connected() - c.handler.InitializeClashMode(newIterator(modeList), currentMode) - if len(modeList) == 0 { - conn.Close() - c.handler.Disconnected(os.ErrInvalid.Error()) - } - }() - } else { - c.handler.Connected() - c.handler.InitializeClashMode(newIterator(modeList), currentMode) - if len(modeList) == 0 { - conn.Close() - c.handler.Disconnected(os.ErrInvalid.Error()) - } - } - if len(modeList) == 0 { - return nil - } - go c.handleModeConn(conn) - case CommandConnections: - err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval) - if err != nil { - return E.Cause(err, "write interval") - } - c.handler.Connected() - go c.handleConnectionsConn(conn) } return nil } func (c *CommandClient) Disconnect() error { - return common.Close(c.conn) + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + if c.cancel != nil { + c.cancel() + } + return common.Close(common.PtrOrNil(c.grpcConn)) +} + +func (c *CommandClient) getClientForCall() (daemon.StartedServiceClient, error) { + c.clientMutex.RLock() + if c.grpcClient != nil { + defer c.clientMutex.RUnlock() + return c.grpcClient, nil + } + c.clientMutex.RUnlock() + + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + + if c.grpcClient != nil { + return c.grpcClient, nil + } + + conn, err := c.grpcDial() + if err != nil { + return nil, err + } + c.grpcConn = conn + c.grpcClient = daemon.NewStartedServiceClient(conn) + if c.ctx == nil { + c.ctx, c.cancel = context.WithCancel(context.Background()) + } + return c.grpcClient, nil +} + +func (c *CommandClient) getStreamContext() (daemon.StartedServiceClient, context.Context) { + c.clientMutex.RLock() + defer c.clientMutex.RUnlock() + return c.grpcClient, c.ctx +} + +func (c *CommandClient) handleLogStream() { + client, ctx := c.getStreamContext() + stream, err := client.SubscribeLog(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + defaultLogLevel, err := client.GetDefaultLogLevel(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.SetDefaultLogLevel(int32(defaultLogLevel.Level)) + for { + logMessage, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + if logMessage.Reset_ { + c.handler.ClearLogs() + } + var messages []*LogEntry + for _, msg := range logMessage.Messages { + messages = append(messages, &LogEntry{ + Level: int32(msg.Level), + Message: msg.Message, + }) + } + c.handler.WriteLogs(newIterator(messages)) + } +} + +func (c *CommandClient) handleStatusStream() { + client, ctx := c.getStreamContext() + interval := c.options.StatusInterval + + stream, err := client.SubscribeStatus(ctx, &daemon.SubscribeStatusRequest{ + Interval: interval, + }) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + for { + status, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.WriteStatus(StatusMessageFromGRPC(status)) + } +} + +func (c *CommandClient) handleGroupStream() { + client, ctx := c.getStreamContext() + + stream, err := client.SubscribeGroups(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + for { + groups, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.WriteGroups(OutboundGroupIteratorFromGRPC(groups)) + } +} + +func (c *CommandClient) handleClashModeStream() { + client, ctx := c.getStreamContext() + + modeStatus, err := client.GetClashModeStatus(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + if sFixAndroidStack { + go func() { + c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode) + if len(modeStatus.ModeList) == 0 { + c.handler.Disconnected(os.ErrInvalid.Error()) + } + }() + } else { + c.handler.InitializeClashMode(newIterator(modeStatus.ModeList), modeStatus.CurrentMode) + if len(modeStatus.ModeList) == 0 { + c.handler.Disconnected(os.ErrInvalid.Error()) + return + } + } + + if len(modeStatus.ModeList) == 0 { + return + } + + stream, err := client.SubscribeClashMode(ctx, &emptypb.Empty{}) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + for { + mode, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + c.handler.UpdateClashMode(mode.Mode) + } +} + +func (c *CommandClient) handleConnectionsStream() { + client, ctx := c.getStreamContext() + interval := c.options.StatusInterval + + stream, err := client.SubscribeConnections(ctx, &daemon.SubscribeConnectionsRequest{ + Interval: interval, + }) + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + + var connections Connections + for { + conns, err := stream.Recv() + if err != nil { + c.handler.Disconnected(err.Error()) + return + } + connections.input = ConnectionsFromGRPC(conns) + c.handler.WriteConnections(&connections) + } +} + +func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SelectOutbound(context.Background(), &daemon.SelectOutboundRequest{ + GroupTag: groupTag, + OutboundTag: outboundTag, + }) + return err +} + +func (c *CommandClient) URLTest(groupTag string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.URLTest(context.Background(), &daemon.URLTestRequest{ + OutboundTag: groupTag, + }) + return err +} + +func (c *CommandClient) SetClashMode(newMode string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SetClashMode(context.Background(), &daemon.ClashMode{ + Mode: newMode, + }) + return err +} + +func (c *CommandClient) CloseConnection(connId string) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.CloseConnection(context.Background(), &daemon.CloseConnectionRequest{ + Id: connId, + }) + return err +} + +func (c *CommandClient) CloseConnections() error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.CloseAllConnections(context.Background(), &emptypb.Empty{}) + return err +} + +func (c *CommandClient) ServiceReload() error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.ReloadService(context.Background(), &emptypb.Empty{}) + return err +} + +func (c *CommandClient) ServiceClose() error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.StopService(context.Background(), &emptypb.Empty{}) + return err +} + +func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { + client, err := c.getClientForCall() + if err != nil { + return nil, err + } + + status, err := client.GetSystemProxyStatus(context.Background(), &emptypb.Empty{}) + if err != nil { + return nil, err + } + return SystemProxyStatusFromGRPC(status), nil +} + +func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SetSystemProxyEnabled(context.Background(), &daemon.SetSystemProxyEnabledRequest{ + Enabled: isEnabled, + }) + return err +} + +func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) { + client, err := c.getClientForCall() + if err != nil { + return nil, err + } + + warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{}) + if err != nil { + return nil, err + } + + var notes []*DeprecatedNote + for _, warning := range warnings.Warnings { + notes = append(notes, &DeprecatedNote{ + Description: warning.Message, + MigrationLink: warning.MigrationLink, + }) + } + return newIterator(notes), nil +} + +func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.SetGroupExpand(context.Background(), &daemon.SetGroupExpandRequest{ + GroupTag: groupTag, + IsExpand: isExpand, + }) + return err } diff --git a/experimental/libbox/command_close_connection.go b/experimental/libbox/command_close_connection.go deleted file mode 100644 index 46f7023f..00000000 --- a/experimental/libbox/command_close_connection.go +++ /dev/null @@ -1,54 +0,0 @@ -package libbox - -import ( - "bufio" - "net" - - "github.com/sagernet/sing-box/experimental/clashapi" - "github.com/sagernet/sing/common/binary" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - - "github.com/gofrs/uuid/v5" -) - -func (c *CommandClient) CloseConnection(connId string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnection)) - if err != nil { - return err - } - writer := bufio.NewWriter(conn) - err = varbin.Write(writer, binary.BigEndian, connId) - if err != nil { - return err - } - err = writer.Flush() - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleCloseConnection(conn net.Conn) error { - reader := bufio.NewReader(conn) - var connId string - err := varbin.Read(reader, binary.BigEndian, &connId) - if err != nil { - return E.Cause(err, "read connection id") - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - targetConn := service.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId)) - if targetConn == nil { - return writeError(conn, E.New("connection already closed")) - } - targetConn.Close() - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_connections.go b/experimental/libbox/command_connections.go deleted file mode 100644 index 39d9303c..00000000 --- a/experimental/libbox/command_connections.go +++ /dev/null @@ -1,269 +0,0 @@ -package libbox - -import ( - "bufio" - "net" - "slices" - "strings" - "time" - - "github.com/sagernet/sing-box/experimental/clashapi" - "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" - "github.com/sagernet/sing/common/binary" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/varbin" - - "github.com/gofrs/uuid/v5" -) - -func (c *CommandClient) handleConnectionsConn(conn net.Conn) { - defer conn.Close() - reader := bufio.NewReader(conn) - var ( - rawConnections []Connection - connections Connections - ) - for { - rawConnections = nil - err := varbin.Read(reader, binary.BigEndian, &rawConnections) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - connections.input = rawConnections - c.handler.WriteConnections(&connections) - } -} - -func (s *CommandServer) handleConnectionsConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - var trafficManager *trafficontrol.Manager - for { - service := s.service - if service != nil { - trafficManager = service.clashServer.(*clashapi.Server).TrafficManager() - break - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } - var ( - connections = make(map[uuid.UUID]*Connection) - outConnections []Connection - ) - writer := bufio.NewWriter(conn) - for { - outConnections = outConnections[:0] - for _, connection := range trafficManager.Connections() { - outConnections = append(outConnections, newConnection(connections, connection, false)) - } - for _, connection := range trafficManager.ClosedConnections() { - outConnections = append(outConnections, newConnection(connections, connection, true)) - } - err = varbin.Write(writer, binary.BigEndian, outConnections) - if err != nil { - return err - } - err = writer.Flush() - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } -} - -const ( - ConnectionStateAll = iota - ConnectionStateActive - ConnectionStateClosed -) - -type Connections struct { - input []Connection - filtered []Connection -} - -func (c *Connections) FilterState(state int32) { - c.filtered = c.filtered[:0] - switch state { - case ConnectionStateAll: - c.filtered = append(c.filtered, c.input...) - case ConnectionStateActive: - for _, connection := range c.input { - if connection.ClosedAt == 0 { - c.filtered = append(c.filtered, connection) - } - } - case ConnectionStateClosed: - for _, connection := range c.input { - if connection.ClosedAt != 0 { - c.filtered = append(c.filtered, connection) - } - } - } -} - -func (c *Connections) SortByDate() { - slices.SortStableFunc(c.filtered, func(x, y Connection) int { - if x.CreatedAt < y.CreatedAt { - return 1 - } else if x.CreatedAt > y.CreatedAt { - return -1 - } else { - return strings.Compare(y.ID, x.ID) - } - }) -} - -func (c *Connections) SortByTraffic() { - slices.SortStableFunc(c.filtered, func(x, y Connection) int { - xTraffic := x.Uplink + x.Downlink - yTraffic := y.Uplink + y.Downlink - if xTraffic < yTraffic { - return 1 - } else if xTraffic > yTraffic { - return -1 - } else { - return strings.Compare(y.ID, x.ID) - } - }) -} - -func (c *Connections) SortByTrafficTotal() { - slices.SortStableFunc(c.filtered, func(x, y Connection) int { - xTraffic := x.UplinkTotal + x.DownlinkTotal - yTraffic := y.UplinkTotal + y.DownlinkTotal - if xTraffic < yTraffic { - return 1 - } else if xTraffic > yTraffic { - return -1 - } else { - return strings.Compare(y.ID, x.ID) - } - }) -} - -func (c *Connections) Iterator() ConnectionIterator { - return newPtrIterator(c.filtered) -} - -type Connection struct { - ID string - Inbound string - InboundType string - IPVersion int32 - Network string - Source string - Destination string - Domain string - Protocol string - User string - FromOutbound string - CreatedAt int64 - ClosedAt int64 - Uplink int64 - Downlink int64 - UplinkTotal int64 - DownlinkTotal int64 - Rule string - Outbound string - OutboundType string - ChainList []string -} - -func (c *Connection) Chain() StringIterator { - return newIterator(c.ChainList) -} - -func (c *Connection) DisplayDestination() string { - destination := M.ParseSocksaddr(c.Destination) - if destination.IsIP() && c.Domain != "" { - destination = M.Socksaddr{ - Fqdn: c.Domain, - Port: destination.Port, - } - return destination.String() - } - return c.Destination -} - -type ConnectionIterator interface { - Next() *Connection - HasNext() bool -} - -func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) Connection { - if oldConnection, loaded := connections[metadata.ID]; loaded { - if isClosed { - if oldConnection.ClosedAt == 0 { - oldConnection.Uplink = 0 - oldConnection.Downlink = 0 - oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli() - } - return *oldConnection - } - lastUplink := oldConnection.UplinkTotal - lastDownlink := oldConnection.DownlinkTotal - uplinkTotal := metadata.Upload.Load() - downlinkTotal := metadata.Download.Load() - oldConnection.Uplink = uplinkTotal - lastUplink - oldConnection.Downlink = downlinkTotal - lastDownlink - oldConnection.UplinkTotal = uplinkTotal - oldConnection.DownlinkTotal = downlinkTotal - return *oldConnection - } - var rule string - if metadata.Rule != nil { - rule = metadata.Rule.String() - } - uplinkTotal := metadata.Upload.Load() - downlinkTotal := metadata.Download.Load() - uplink := uplinkTotal - downlink := downlinkTotal - var closedAt int64 - if !metadata.ClosedAt.IsZero() { - closedAt = metadata.ClosedAt.UnixMilli() - uplink = 0 - downlink = 0 - } - connection := Connection{ - ID: metadata.ID.String(), - Inbound: metadata.Metadata.Inbound, - InboundType: metadata.Metadata.InboundType, - IPVersion: int32(metadata.Metadata.IPVersion), - Network: metadata.Metadata.Network, - Source: metadata.Metadata.Source.String(), - Destination: metadata.Metadata.Destination.String(), - Domain: metadata.Metadata.Domain, - Protocol: metadata.Metadata.Protocol, - User: metadata.Metadata.User, - FromOutbound: metadata.Metadata.Outbound, - CreatedAt: metadata.CreatedAt.UnixMilli(), - ClosedAt: closedAt, - Uplink: uplink, - Downlink: downlink, - UplinkTotal: uplinkTotal, - DownlinkTotal: downlinkTotal, - Rule: rule, - Outbound: metadata.Outbound, - OutboundType: metadata.OutboundType, - ChainList: metadata.Chain, - } - connections[metadata.ID] = &connection - return connection -} diff --git a/experimental/libbox/command_conntrack.go b/experimental/libbox/command_conntrack.go deleted file mode 100644 index cf8389a6..00000000 --- a/experimental/libbox/command_conntrack.go +++ /dev/null @@ -1,28 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - runtimeDebug "runtime/debug" - "time" - - "github.com/sagernet/sing-box/common/conntrack" -) - -func (c *CommandClient) CloseConnections() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - return binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnections)) -} - -func (s *CommandServer) handleCloseConnections(conn net.Conn) error { - conntrack.Close() - go func() { - time.Sleep(time.Second) - runtimeDebug.FreeOSMemory() - }() - return nil -} diff --git a/experimental/libbox/command_deprecated_report.go b/experimental/libbox/command_deprecated_report.go deleted file mode 100644 index 5772124c..00000000 --- a/experimental/libbox/command_deprecated_report.go +++ /dev/null @@ -1,46 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - "github.com/sagernet/sing-box/experimental/deprecated" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - "github.com/sagernet/sing/service" -) - -func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) { - conn, err := c.directConnect() - if err != nil { - return nil, err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGetDeprecatedNotes)) - if err != nil { - return nil, err - } - err = readError(conn) - if err != nil { - return nil, err - } - var features []deprecated.Note - err = varbin.Read(conn, binary.BigEndian, &features) - if err != nil { - return nil, err - } - return newIterator(common.Map(features, func(it deprecated.Note) *DeprecatedNote { return (*DeprecatedNote)(&it) })), nil -} - -func (s *CommandServer) handleGetDeprecatedNotes(conn net.Conn) error { - boxService := s.service - if boxService == nil { - return writeError(conn, E.New("service not ready")) - } - err := writeError(conn, nil) - if err != nil { - return err - } - return varbin.Write(conn, binary.BigEndian, service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get()) -} diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go deleted file mode 100644 index 684cac62..00000000 --- a/experimental/libbox/command_group.go +++ /dev/null @@ -1,198 +0,0 @@ -package libbox - -import ( - "bufio" - "encoding/binary" - "io" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/protocol/group" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - "github.com/sagernet/sing/service" -) - -func (c *CommandClient) handleGroupConn(conn net.Conn) { - defer conn.Close() - - for { - groups, err := readGroups(conn) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteGroups(groups) - } -} - -func (s *CommandServer) handleGroupConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - writer := bufio.NewWriter(conn) - for { - service := s.service - if service != nil { - err = writeGroups(writer, service) - if err != nil { - return err - } - } else { - err = binary.Write(writer, binary.BigEndian, uint16(0)) - if err != nil { - return err - } - } - err = writer.Flush() - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-s.urlTestUpdate: - } - } -} - -type OutboundGroup struct { - Tag string - Type string - Selectable bool - Selected string - IsExpand bool - ItemList []*OutboundGroupItem -} - -func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { - return newIterator(g.ItemList) -} - -type OutboundGroupIterator interface { - Next() *OutboundGroup - HasNext() bool -} - -type OutboundGroupItem struct { - Tag string - Type string - URLTestTime int64 - URLTestDelay int32 -} - -type OutboundGroupItemIterator interface { - Next() *OutboundGroupItem - HasNext() bool -} - -func readGroups(reader io.Reader) (OutboundGroupIterator, error) { - groups, err := varbin.ReadValue[[]*OutboundGroup](reader, binary.BigEndian) - if err != nil { - return nil, err - } - return newIterator(groups), nil -} - -func writeGroups(writer io.Writer, boxService *BoxService) error { - historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx) - cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx) - outbounds := boxService.instance.Outbound().Outbounds() - var iGroups []adapter.OutboundGroup - for _, it := range outbounds { - if group, isGroup := it.(adapter.OutboundGroup); isGroup { - iGroups = append(iGroups, group) - } - } - var groups []OutboundGroup - for _, iGroup := range iGroups { - var outboundGroup OutboundGroup - outboundGroup.Tag = iGroup.Tag() - outboundGroup.Type = iGroup.Type() - _, outboundGroup.Selectable = iGroup.(*group.Selector) - outboundGroup.Selected = iGroup.Now() - if cacheFile != nil { - if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded { - outboundGroup.IsExpand = isExpand - } - } - - for _, itemTag := range iGroup.All() { - itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag) - if !isLoaded { - continue - } - - var item OutboundGroupItem - item.Tag = itemTag - item.Type = itemOutbound.Type() - if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil { - item.URLTestTime = history.Time.Unix() - item.URLTestDelay = int32(history.Delay) - } - outboundGroup.ItemList = append(outboundGroup.ItemList, &item) - } - if len(outboundGroup.ItemList) < 2 { - continue - } - groups = append(groups, outboundGroup) - } - return varbin.Write(writer, binary.BigEndian, groups) -} - -func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, groupTag) - if err != nil { - return err - } - err = binary.Write(conn, binary.BigEndian, isExpand) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error { - groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - var isExpand bool - err = binary.Read(conn, binary.BigEndian, &isExpand) - if err != nil { - return err - } - serviceNow := s.service - if serviceNow == nil { - return writeError(conn, E.New("service not ready")) - } - cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx) - if cacheFile != nil { - err = cacheFile.StoreGroupExpand(groupTag, isExpand) - if err != nil { - return writeError(conn, err) - } - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_log.go b/experimental/libbox/command_log.go deleted file mode 100644 index 07f6e839..00000000 --- a/experimental/libbox/command_log.go +++ /dev/null @@ -1,160 +0,0 @@ -package libbox - -import ( - "bufio" - "context" - "io" - "net" - "time" - - "github.com/sagernet/sing/common/binary" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func (s *CommandServer) ResetLog() { - s.access.Lock() - defer s.access.Unlock() - s.savedLines.Init() - select { - case s.logReset <- struct{}{}: - default: - } -} - -func (s *CommandServer) WriteMessage(message string) { - s.subscriber.Emit(message) - s.access.Lock() - s.savedLines.PushBack(message) - if s.savedLines.Len() > s.maxLines { - s.savedLines.Remove(s.savedLines.Front()) - } - s.access.Unlock() -} - -func (s *CommandServer) handleLogConn(conn net.Conn) error { - var ( - interval int64 - timer *time.Timer - ) - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - timer = time.NewTimer(time.Duration(interval)) - if !timer.Stop() { - <-timer.C - } - var savedLines []string - s.access.Lock() - savedLines = make([]string, 0, s.savedLines.Len()) - for element := s.savedLines.Front(); element != nil; element = element.Next() { - savedLines = append(savedLines, element.Value) - } - s.access.Unlock() - subscription, done, err := s.observer.Subscribe() - if err != nil { - return err - } - defer s.observer.UnSubscribe(subscription) - writer := bufio.NewWriter(conn) - select { - case <-s.logReset: - err = writer.WriteByte(1) - if err != nil { - return err - } - err = writer.Flush() - if err != nil { - return err - } - default: - } - if len(savedLines) > 0 { - err = writer.WriteByte(0) - if err != nil { - return err - } - err = varbin.Write(writer, binary.BigEndian, savedLines) - if err != nil { - return err - } - } - ctx := connKeepAlive(conn) - var logLines []string - for { - err = writer.Flush() - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-s.logReset: - err = writer.WriteByte(1) - if err != nil { - return err - } - case <-done: - return nil - case logLine := <-subscription: - logLines = logLines[:0] - logLines = append(logLines, logLine) - timer.Reset(time.Duration(interval)) - loopLogs: - for { - select { - case logLine = <-subscription: - logLines = append(logLines, logLine) - case <-timer.C: - break loopLogs - } - } - err = writer.WriteByte(0) - if err != nil { - return err - } - err = varbin.Write(writer, binary.BigEndian, logLines) - if err != nil { - return err - } - } - } -} - -func (c *CommandClient) handleLogConn(conn net.Conn) { - reader := bufio.NewReader(conn) - for { - messageType, err := reader.ReadByte() - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - var messages []string - switch messageType { - case 0: - err = varbin.Read(reader, binary.BigEndian, &messages) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteLogs(newIterator(messages)) - case 1: - c.handler.ClearLogs() - } - } -} - -func connKeepAlive(reader io.Reader) context.Context { - ctx, cancel := context.WithCancelCause(context.Background()) - go func() { - for { - _, err := reader.Read(make([]byte, 1)) - if err != nil { - cancel(err) - return - } - } - }() - return ctx -} diff --git a/experimental/libbox/command_power.go b/experimental/libbox/command_power.go deleted file mode 100644 index 00906490..00000000 --- a/experimental/libbox/command_power.go +++ /dev/null @@ -1,59 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - "github.com/sagernet/sing/common/varbin" -) - -func (c *CommandClient) ServiceReload() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceReload)) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleServiceReload(conn net.Conn) error { - rErr := s.handler.ServiceReload() - err := binary.Write(conn, binary.BigEndian, rErr != nil) - if err != nil { - return err - } - if rErr != nil { - return varbin.Write(conn, binary.BigEndian, rErr.Error()) - } - return nil -} - -func (c *CommandClient) ServiceClose() error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceClose)) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleServiceClose(conn net.Conn) error { - rErr := s.service.Close() - s.handler.PostServiceClose() - err := binary.Write(conn, binary.BigEndian, rErr != nil) - if err != nil { - return err - } - if rErr != nil { - return varbin.Write(conn, binary.BigEndian, rErr.Error()) - } - return nil -} diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go deleted file mode 100644 index 6dd74a2d..00000000 --- a/experimental/libbox/command_select.go +++ /dev/null @@ -1,58 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - - "github.com/sagernet/sing-box/protocol/group" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSelectOutbound)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, groupTag) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, outboundTag) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { - groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - outboundTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - service := s.service - if service == nil { - return writeError(conn, E.New("service not ready")) - } - outboundGroup, isLoaded := service.instance.Outbound().Outbound(groupTag) - if !isLoaded { - return writeError(conn, E.New("selector not found: ", groupTag)) - } - selector, isSelector := outboundGroup.(*group.Selector) - if !isSelector { - return writeError(conn, E.New("outbound is not a selector: ", groupTag)) - } - if !selector.SelectOutbound(outboundTag) { - return writeError(conn, E.New("outbound not found in selector: ", outboundTag)) - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 798a52bd..5195c0b1 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -1,182 +1,266 @@ package libbox import ( - "encoding/binary" + "context" + "errors" "net" "os" "path/filepath" - "sync" + "strconv" + "syscall" + "time" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/experimental/clashapi" + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/daemon" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/observable" - "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) type CommandServer struct { - listener net.Listener - handler CommandServerHandler - - access sync.Mutex - savedLines list.List[string] - maxLines int - subscriber *observable.Subscriber[string] - observer *observable.Observer[string] - service *BoxService - - // These channels only work with a single client. if multi-client support is needed, replace with Subscriber/Observer - urlTestUpdate chan struct{} - modeUpdate chan struct{} - logReset chan struct{} - - closedConnections []Connection + *daemon.StartedService + handler CommandServerHandler + platformInterface PlatformInterface + platformWrapper *platformInterfaceWrapper + grpcServer *grpc.Server + listener net.Listener + endPauseTimer *time.Timer } type CommandServerHandler interface { + ServiceStop() error ServiceReload() error - PostServiceClose() - GetSystemProxyStatus() *SystemProxyStatus - SetSystemProxyEnabled(isEnabled bool) error + GetSystemProxyStatus() (*SystemProxyStatus, error) + SetSystemProxyEnabled(enabled bool) error + WriteDebugMessage(message string) } -func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServer { +func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) { + ctx := BaseContext(platformInterface) + platformWrapper := &platformInterfaceWrapper{ + iif: platformInterface, + useProcFS: platformInterface.UseProcFS(), + } + service.MustRegister[adapter.PlatformInterface](ctx, platformWrapper) server := &CommandServer{ - handler: handler, - maxLines: int(maxLines), - subscriber: observable.NewSubscriber[string](128), - urlTestUpdate: make(chan struct{}, 1), - modeUpdate: make(chan struct{}, 1), - logReset: make(chan struct{}, 1), + handler: handler, + platformInterface: platformInterface, + platformWrapper: platformWrapper, } - server.observer = observable.NewObserver[string](server.subscriber, 64) - return server + server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{ + Context: ctx, + // Platform: platformWrapper, + Handler: (*platformHandler)(server), + Debug: sDebug, + LogMaxLines: sLogMaxLines, + // WorkingDirectory: sWorkingPath, + // TempDirectory: sTempPath, + // UserID: sUserID, + // GroupID: sGroupID, + // SystemProxyEnabled: false, + }) + return server, nil } -func (s *CommandServer) SetService(newService *BoxService) { - if newService != nil { - service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate) - newService.clashServer.(*clashapi.Server).SetModeUpdateHook(s.modeUpdate) +func unaryAuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if sCommandServerSecret == "" { + return handler(ctx, req) } - s.service = newService - s.notifyURLTestUpdate() + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Error(codes.Unauthenticated, "missing metadata") + } + values := md.Get("x-command-secret") + if len(values) == 0 { + return nil, status.Error(codes.Unauthenticated, "missing authentication secret") + } + if values[0] != sCommandServerSecret { + return nil, status.Error(codes.Unauthenticated, "invalid authentication secret") + } + return handler(ctx, req) } -func (s *CommandServer) notifyURLTestUpdate() { - select { - case s.urlTestUpdate <- struct{}{}: - default: +func streamAuthInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + if sCommandServerSecret == "" { + return handler(srv, ss) } + md, ok := metadata.FromIncomingContext(ss.Context()) + if !ok { + return status.Error(codes.Unauthenticated, "missing metadata") + } + values := md.Get("x-command-secret") + if len(values) == 0 { + return status.Error(codes.Unauthenticated, "missing authentication secret") + } + if values[0] != sCommandServerSecret { + return status.Error(codes.Unauthenticated, "invalid authentication secret") + } + return handler(srv, ss) } func (s *CommandServer) Start() error { - if !sTVOS { - return s.listenUNIX() - } else { - return s.listenTCP() - } -} - -func (s *CommandServer) listenUNIX() error { - sockPath := filepath.Join(sBasePath, "command.sock") - os.Remove(sockPath) - listener, err := net.ListenUnix("unix", &net.UnixAddr{ - Name: sockPath, - Net: "unix", - }) - if err != nil { - return E.Cause(err, "listen ", sockPath) - } - err = os.Chown(sockPath, sUserID, sGroupID) - if err != nil { - listener.Close() - os.Remove(sockPath) - return E.Cause(err, "chown") - } - s.listener = listener - go s.loopConnection(listener) - return nil -} - -func (s *CommandServer) listenTCP() error { - listener, err := net.Listen("tcp", "127.0.0.1:8964") - if err != nil { - return E.Cause(err, "listen") - } - s.listener = listener - go s.loopConnection(listener) - return nil -} - -func (s *CommandServer) Close() error { - return common.Close( - s.listener, - s.observer, + var ( + listener net.Listener + err error ) -} - -func (s *CommandServer) loopConnection(listener net.Listener) { - for { - conn, err := listener.Accept() - if err != nil { - return - } - go func() { - hErr := s.handleConnection(conn) - if hErr != nil && !E.IsClosed(err) { - if debug.Enabled { - log.Warn("log-server: process connection: ", hErr) - } + if sCommandServerListenPort == 0 { + sockPath := filepath.Join(sBasePath, "command.sock") + os.Remove(sockPath) + for i := 0; i < 30; i++ { + listener, err = net.ListenUnix("unix", &net.UnixAddr{ + Name: sockPath, + Net: "unix", + }) + if err == nil { + break } - }() + if !errors.Is(err, syscall.EROFS) { + break + } + time.Sleep(time.Second) + } + if err != nil { + return E.Cause(err, "listen command server") + } + if sUserID != os.Getuid() { + err = os.Chown(sockPath, sUserID, sGroupID) + if err != nil { + listener.Close() + os.Remove(sockPath) + return E.Cause(err, "chown") + } + } + } else { + listener, err = net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort)))) + if err != nil { + return E.Cause(err, "listen command server") + } + } + s.listener = listener + serverOptions := []grpc.ServerOption{ + grpc.UnaryInterceptor(unaryAuthInterceptor), + grpc.StreamInterceptor(streamAuthInterceptor), + } + s.grpcServer = grpc.NewServer(serverOptions...) + daemon.RegisterStartedServiceServer(s.grpcServer, s.StartedService) + go s.grpcServer.Serve(listener) + return nil +} + +func (s *CommandServer) Close() { + if s.grpcServer != nil { + s.grpcServer.Stop() + } + common.Close(s.listener) +} + +type OverrideOptions struct { + AutoRedirect bool + IncludePackage StringIterator + ExcludePackage StringIterator +} + +func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error { + return s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{ + AutoRedirect: options.AutoRedirect, + IncludePackage: iteratorToArray(options.IncludePackage), + ExcludePackage: iteratorToArray(options.ExcludePackage), + }) +} + +func (s *CommandServer) CloseService() error { + return s.StartedService.CloseService() +} + +func (s *CommandServer) WriteMessage(level int32, message string) { + s.StartedService.WriteMessage(log.Level(level), message) +} + +func (s *CommandServer) SetError(message string) { + s.StartedService.SetError(E.New(message)) +} + +func (s *CommandServer) NeedWIFIState() bool { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return false + } + return instance.Box().Router().NeedWIFIState() +} + +func (s *CommandServer) Pause() { + instance := s.StartedService.Instance() + if instance == nil || instance.PauseManager() == nil { + return + } + instance.PauseManager().DevicePause() + if C.IsIos { + if s.endPauseTimer == nil { + s.endPauseTimer = time.AfterFunc(time.Minute, instance.PauseManager().DeviceWake) + } else { + s.endPauseTimer.Reset(time.Minute) + } } } -func (s *CommandServer) handleConnection(conn net.Conn) error { - defer conn.Close() - var command uint8 - err := binary.Read(conn, binary.BigEndian, &command) - if err != nil { - return E.Cause(err, "read command") +func (s *CommandServer) Wake() { + instance := s.StartedService.Instance() + if instance == nil || instance.PauseManager() == nil { + return } - switch int32(command) { - case CommandLog: - return s.handleLogConn(conn) - case CommandStatus: - return s.handleStatusConn(conn) - case CommandServiceReload: - return s.handleServiceReload(conn) - case CommandServiceClose: - return s.handleServiceClose(conn) - case CommandCloseConnections: - return s.handleCloseConnections(conn) - case CommandGroup: - return s.handleGroupConn(conn) - case CommandSelectOutbound: - return s.handleSelectOutbound(conn) - case CommandURLTest: - return s.handleURLTest(conn) - case CommandGroupExpand: - return s.handleSetGroupExpand(conn) - case CommandClashMode: - return s.handleModeConn(conn) - case CommandSetClashMode: - return s.handleSetClashMode(conn) - case CommandGetSystemProxyStatus: - return s.handleGetSystemProxyStatus(conn) - case CommandSetSystemProxyEnabled: - return s.handleSetSystemProxyEnabled(conn) - case CommandConnections: - return s.handleConnectionsConn(conn) - case CommandCloseConnection: - return s.handleCloseConnection(conn) - case CommandGetDeprecatedNotes: - return s.handleGetDeprecatedNotes(conn) - default: - return E.New("unknown command: ", command) + if !C.IsIos { + instance.PauseManager().DeviceWake() } } + +func (s *CommandServer) ResetNetwork() { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return + } + instance.Box().Router().ResetNetwork() +} + +func (s *CommandServer) UpdateWIFIState() { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return + } + instance.Box().Network().UpdateWIFIState() +} + +type platformHandler CommandServer + +func (h *platformHandler) ServiceStop() error { + return (*CommandServer)(h).handler.ServiceStop() +} + +func (h *platformHandler) ServiceReload() error { + return (*CommandServer)(h).handler.ServiceReload() +} + +func (h *platformHandler) SystemProxyStatus() (*daemon.SystemProxyStatus, error) { + status, err := (*CommandServer)(h).handler.GetSystemProxyStatus() + if err != nil { + return nil, err + } + return &daemon.SystemProxyStatus{ + Enabled: status.Enabled, + Available: status.Available, + }, nil +} + +func (h *platformHandler) SetSystemProxyEnabled(enabled bool) error { + return (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled) +} + +func (h *platformHandler) WriteDebugMessage(message string) { + (*CommandServer)(h).handler.WriteDebugMessage(message) +} diff --git a/experimental/libbox/command_shared.go b/experimental/libbox/command_shared.go deleted file mode 100644 index b98c2e5d..00000000 --- a/experimental/libbox/command_shared.go +++ /dev/null @@ -1,39 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "io" - - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" -) - -func readError(reader io.Reader) error { - var hasError bool - err := binary.Read(reader, binary.BigEndian, &hasError) - if err != nil { - return err - } - if hasError { - errorMessage, err := varbin.ReadValue[string](reader, binary.BigEndian) - if err != nil { - return err - } - return E.New(errorMessage) - } - return nil -} - -func writeError(writer io.Writer, wErr error) error { - err := binary.Write(writer, binary.BigEndian, wErr != nil) - if err != nil { - return err - } - if wErr != nil { - err = varbin.Write(writer, binary.BigEndian, wErr.Error()) - if err != nil { - return err - } - } - return nil -} diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go deleted file mode 100644 index f8709ef0..00000000 --- a/experimental/libbox/command_status.go +++ /dev/null @@ -1,85 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - "runtime" - "time" - - "github.com/sagernet/sing-box/common/conntrack" - "github.com/sagernet/sing-box/experimental/clashapi" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/memory" -) - -type StatusMessage struct { - Memory int64 - Goroutines int32 - ConnectionsIn int32 - ConnectionsOut int32 - TrafficAvailable bool - Uplink int64 - Downlink int64 - UplinkTotal int64 - DownlinkTotal int64 -} - -func (s *CommandServer) readStatus() StatusMessage { - var message StatusMessage - message.Memory = int64(memory.Inuse()) - message.Goroutines = int32(runtime.NumGoroutine()) - message.ConnectionsOut = int32(conntrack.Count()) - - if s.service != nil { - message.TrafficAvailable = true - trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager() - message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() - message.ConnectionsIn = int32(trafficManager.ConnectionsLen()) - } - - return message -} - -func (s *CommandServer) handleStatusConn(conn net.Conn) error { - var interval int64 - err := binary.Read(conn, binary.BigEndian, &interval) - if err != nil { - return E.Cause(err, "read interval") - } - ticker := time.NewTicker(time.Duration(interval)) - defer ticker.Stop() - ctx := connKeepAlive(conn) - status := s.readStatus() - uploadTotal := status.UplinkTotal - downloadTotal := status.DownlinkTotal - for { - err = binary.Write(conn, binary.BigEndian, status) - if err != nil { - return err - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - status = s.readStatus() - upload := status.UplinkTotal - uploadTotal - download := status.DownlinkTotal - downloadTotal - uploadTotal = status.UplinkTotal - downloadTotal = status.DownlinkTotal - status.Uplink = upload - status.Downlink = download - } -} - -func (c *CommandClient) handleStatusConn(conn net.Conn) { - for { - var message StatusMessage - err := binary.Read(conn, binary.BigEndian, &message) - if err != nil { - c.handler.Disconnected(err.Error()) - return - } - c.handler.WriteStatus(&message) - } -} diff --git a/experimental/libbox/command_system_proxy.go b/experimental/libbox/command_system_proxy.go deleted file mode 100644 index 8a534ae8..00000000 --- a/experimental/libbox/command_system_proxy.go +++ /dev/null @@ -1,80 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" -) - -type SystemProxyStatus struct { - Available bool - Enabled bool -} - -func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { - conn, err := c.directConnectWithRetry() - if err != nil { - return nil, err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandGetSystemProxyStatus)) - if err != nil { - return nil, err - } - var status SystemProxyStatus - err = binary.Read(conn, binary.BigEndian, &status.Available) - if err != nil { - return nil, err - } - if status.Available { - err = binary.Read(conn, binary.BigEndian, &status.Enabled) - if err != nil { - return nil, err - } - } - return &status, nil -} - -func (s *CommandServer) handleGetSystemProxyStatus(conn net.Conn) error { - status := s.handler.GetSystemProxyStatus() - err := binary.Write(conn, binary.BigEndian, status.Available) - if err != nil { - return err - } - if status.Available { - err = binary.Write(conn, binary.BigEndian, status.Enabled) - if err != nil { - return err - } - } - return nil -} - -func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandSetSystemProxyEnabled)) - if err != nil { - return err - } - err = binary.Write(conn, binary.BigEndian, isEnabled) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleSetSystemProxyEnabled(conn net.Conn) error { - var isEnabled bool - err := binary.Read(conn, binary.BigEndian, &isEnabled) - if err != nil { - return err - } - err = s.handler.SetSystemProxyEnabled(isEnabled) - if err != nil { - return writeError(conn, err) - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/command_types.go b/experimental/libbox/command_types.go new file mode 100644 index 00000000..9383d04b --- /dev/null +++ b/experimental/libbox/command_types.go @@ -0,0 +1,276 @@ +package libbox + +import ( + "slices" + "strings" + + "github.com/sagernet/sing-box/daemon" + M "github.com/sagernet/sing/common/metadata" +) + +type StatusMessage struct { + Memory int64 + Goroutines int32 + ConnectionsIn int32 + ConnectionsOut int32 + TrafficAvailable bool + Uplink int64 + Downlink int64 + UplinkTotal int64 + DownlinkTotal int64 +} + +type SystemProxyStatus struct { + Available bool + Enabled bool +} + +type OutboundGroup struct { + Tag string + Type string + Selectable bool + Selected string + IsExpand bool + ItemList []*OutboundGroupItem +} + +func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { + return newIterator(g.ItemList) +} + +type OutboundGroupIterator interface { + Next() *OutboundGroup + HasNext() bool +} + +type OutboundGroupItem struct { + Tag string + Type string + URLTestTime int64 + URLTestDelay int32 +} + +type OutboundGroupItemIterator interface { + Next() *OutboundGroupItem + HasNext() bool +} + +const ( + ConnectionStateAll = iota + ConnectionStateActive + ConnectionStateClosed +) + +type Connections struct { + input []Connection + filtered []Connection +} + +func (c *Connections) FilterState(state int32) { + c.filtered = c.filtered[:0] + switch state { + case ConnectionStateAll: + c.filtered = append(c.filtered, c.input...) + case ConnectionStateActive: + for _, connection := range c.input { + if connection.ClosedAt == 0 { + c.filtered = append(c.filtered, connection) + } + } + case ConnectionStateClosed: + for _, connection := range c.input { + if connection.ClosedAt != 0 { + c.filtered = append(c.filtered, connection) + } + } + } +} + +func (c *Connections) SortByDate() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + if x.CreatedAt < y.CreatedAt { + return 1 + } else if x.CreatedAt > y.CreatedAt { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) SortByTraffic() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + xTraffic := x.Uplink + x.Downlink + yTraffic := y.Uplink + y.Downlink + if xTraffic < yTraffic { + return 1 + } else if xTraffic > yTraffic { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) SortByTrafficTotal() { + slices.SortStableFunc(c.filtered, func(x, y Connection) int { + xTraffic := x.UplinkTotal + x.DownlinkTotal + yTraffic := y.UplinkTotal + y.DownlinkTotal + if xTraffic < yTraffic { + return 1 + } else if xTraffic > yTraffic { + return -1 + } else { + return strings.Compare(y.ID, x.ID) + } + }) +} + +func (c *Connections) Iterator() ConnectionIterator { + return newPtrIterator(c.filtered) +} + +type Connection struct { + ID string + Inbound string + InboundType string + IPVersion int32 + Network string + Source string + Destination string + Domain string + Protocol string + User string + FromOutbound string + CreatedAt int64 + ClosedAt int64 + Uplink int64 + Downlink int64 + UplinkTotal int64 + DownlinkTotal int64 + Rule string + Outbound string + OutboundType string + ChainList []string +} + +func (c *Connection) Chain() StringIterator { + return newIterator(c.ChainList) +} + +func (c *Connection) DisplayDestination() string { + destination := M.ParseSocksaddr(c.Destination) + if destination.IsIP() && c.Domain != "" { + destination = M.Socksaddr{ + Fqdn: c.Domain, + Port: destination.Port, + } + return destination.String() + } + return c.Destination +} + +type ConnectionIterator interface { + Next() *Connection + HasNext() bool +} + +func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage { + if status == nil { + return nil + } + return &StatusMessage{ + Memory: int64(status.Memory), + Goroutines: status.Goroutines, + ConnectionsIn: status.ConnectionsIn, + ConnectionsOut: status.ConnectionsOut, + TrafficAvailable: status.TrafficAvailable, + Uplink: status.Uplink, + Downlink: status.Downlink, + UplinkTotal: status.UplinkTotal, + DownlinkTotal: status.DownlinkTotal, + } +} + +func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator { + if groups == nil || len(groups.Group) == 0 { + return newIterator([]*OutboundGroup{}) + } + var libboxGroups []*OutboundGroup + for _, g := range groups.Group { + libboxGroup := &OutboundGroup{ + Tag: g.Tag, + Type: g.Type, + Selectable: g.Selectable, + Selected: g.Selected, + IsExpand: g.IsExpand, + } + for _, item := range g.Items { + libboxGroup.ItemList = append(libboxGroup.ItemList, &OutboundGroupItem{ + Tag: item.Tag, + Type: item.Type, + URLTestTime: item.UrlTestTime, + URLTestDelay: item.UrlTestDelay, + }) + } + libboxGroups = append(libboxGroups, libboxGroup) + } + return newIterator(libboxGroups) +} + +func ConnectionFromGRPC(conn *daemon.Connection) Connection { + return Connection{ + ID: conn.Id, + Inbound: conn.Inbound, + InboundType: conn.InboundType, + IPVersion: conn.IpVersion, + Network: conn.Network, + Source: conn.Source, + Destination: conn.Destination, + Domain: conn.Domain, + Protocol: conn.Protocol, + User: conn.User, + FromOutbound: conn.FromOutbound, + CreatedAt: conn.CreatedAt, + ClosedAt: conn.ClosedAt, + Uplink: conn.Uplink, + Downlink: conn.Downlink, + UplinkTotal: conn.UplinkTotal, + DownlinkTotal: conn.DownlinkTotal, + Rule: conn.Rule, + Outbound: conn.Outbound, + OutboundType: conn.OutboundType, + ChainList: conn.ChainList, + } +} + +func ConnectionsFromGRPC(connections *daemon.Connections) []Connection { + if connections == nil || len(connections.Connections) == 0 { + return nil + } + var libboxConnections []Connection + for _, conn := range connections.Connections { + libboxConnections = append(libboxConnections, ConnectionFromGRPC(conn)) + } + return libboxConnections +} + +func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus { + if status == nil { + return nil + } + return &SystemProxyStatus{ + Available: status.Available, + Enabled: status.Enabled, + } +} + +func SystemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus { + if status == nil { + return nil + } + return &daemon.SystemProxyStatus{ + Available: status.Available, + Enabled: status.Enabled, + } +} diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go deleted file mode 100644 index 907d1699..00000000 --- a/experimental/libbox/command_urltest.go +++ /dev/null @@ -1,86 +0,0 @@ -package libbox - -import ( - "encoding/binary" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/protocol/group" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/batch" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" - "github.com/sagernet/sing/service" -) - -func (c *CommandClient) URLTest(groupTag string) error { - conn, err := c.directConnect() - if err != nil { - return err - } - defer conn.Close() - err = binary.Write(conn, binary.BigEndian, uint8(CommandURLTest)) - if err != nil { - return err - } - err = varbin.Write(conn, binary.BigEndian, groupTag) - if err != nil { - return err - } - return readError(conn) -} - -func (s *CommandServer) handleURLTest(conn net.Conn) error { - groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian) - if err != nil { - return err - } - serviceNow := s.service - if serviceNow == nil { - return nil - } - abstractOutboundGroup, isLoaded := serviceNow.instance.Outbound().Outbound(groupTag) - if !isLoaded { - return writeError(conn, E.New("outbound group not found: ", groupTag)) - } - outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup) - if !isOutboundGroup { - return writeError(conn, E.New("outbound is not a group: ", groupTag)) - } - urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) - if isURLTest { - go urlTest.CheckOutbounds() - } else { - historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx) - outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { - itOutbound, _ := serviceNow.instance.Outbound().Outbound(it) - return itOutbound - }), func(it adapter.Outbound) bool { - if it == nil { - return false - } - _, isGroup := it.(adapter.OutboundGroup) - return !isGroup - }) - b, _ := batch.New(serviceNow.ctx, batch.WithConcurrencyNum[any](10)) - for _, detour := range outbounds { - outboundToTest := detour - outboundTag := outboundToTest.Tag() - b.Go(outboundTag, func() (any, error) { - t, err := urltest.URLTest(serviceNow.ctx, "", outboundToTest) - if err != nil { - historyStorage.DeleteURLTestHistory(outboundTag) - } else { - historyStorage.StoreURLTestHistory(outboundTag, &adapter.URLTestHistory{ - Time: time.Now(), - Delay: t, - }) - } - return nil, nil - }) - } - } - return writeError(conn, nil) -} diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 5df7d141..946eea26 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -3,19 +3,16 @@ package libbox import ( "bytes" "context" - "net/netip" "os" - "github.com/sagernet/sing-box" + box "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -55,7 +52,7 @@ func CheckConfig(configContent string) error { } ctx, cancel := context.WithCancel(ctx) defer cancel() - ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil)) + ctx = service.ContextWith[adapter.PlatformInterface](ctx, (*platformInterfaceStub)(nil)) instance, err := box.New(box.Options{ Context: ctx, Options: options, @@ -80,7 +77,11 @@ func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error { return nil } -func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { +func (s *platformInterfaceStub) UsePlatformInterface() bool { + return false +} + +func (s *platformInterfaceStub) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { return nil, os.ErrInvalid } @@ -92,7 +93,11 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg return (*interfaceMonitorStub)(nil) } -func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) { +func (s *platformInterfaceStub) UsePlatformNetworkInterfaces() bool { + return false +} + +func (s *platformInterfaceStub) NetworkInterfaces() ([]adapter.NetworkInterface, error) { return nil, os.ErrInvalid } @@ -100,15 +105,15 @@ func (s *platformInterfaceStub) UnderNetworkExtension() bool { return false } -func (s *platformInterfaceStub) IncludeAllNetworks() bool { +func (s *platformInterfaceStub) NetworkExtensionIncludeAllNetworks() bool { return false } func (s *platformInterfaceStub) ClearDNSCache() { } -func (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool { - return false +func (s *platformInterfaceStub) RequestPermissionForWIFIState() error { + return nil } func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { @@ -119,11 +124,27 @@ func (s *platformInterfaceStub) SystemCertificates() []string { return nil } -func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { +func (s *platformInterfaceStub) UsePlatformConnectionOwnerFinder() bool { + return false +} + +func (s *platformInterfaceStub) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) { return nil, os.ErrInvalid } -func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error { +func (s *platformInterfaceStub) UsePlatformNotification() bool { + return false +} + +func (s *platformInterfaceStub) SendNotification(notification *adapter.Notification) error { + return nil +} + +func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool { + return false +} + +func (s *platformInterfaceStub) LocalDNSTransport() dns.TransportConstructorFunc[option.LocalDNSServerOptions] { return nil } diff --git a/experimental/libbox/deprecated.go b/experimental/libbox/deprecated.go index f85b7747..0c2f8d8a 100644 --- a/experimental/libbox/deprecated.go +++ b/experimental/libbox/deprecated.go @@ -1,33 +1,9 @@ package libbox import ( - "sync" - "github.com/sagernet/sing-box/experimental/deprecated" - "github.com/sagernet/sing/common" ) -var _ deprecated.Manager = (*deprecatedManager)(nil) - -type deprecatedManager struct { - access sync.Mutex - notes []deprecated.Note -} - -func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) { - m.access.Lock() - defer m.access.Unlock() - m.notes = common.Uniq(append(m.notes, feature)) -} - -func (m *deprecatedManager) Get() []deprecated.Note { - m.access.Lock() - defer m.access.Unlock() - notes := m.notes - m.notes = nil - return notes -} - var _ = deprecated.Note(DeprecatedNote{}) type DeprecatedNote struct { diff --git a/experimental/libbox/http.go b/experimental/libbox/http.go index e037de00..9f4b2915 100644 --- a/experimental/libbox/http.go +++ b/experimental/libbox/http.go @@ -77,22 +77,27 @@ func NewHTTPClient() HTTPClient { } func (c *httpClient) ModernTLS() { - c.tls.MinVersion = tls.VersionTLS12 - c.tls.CipherSuites = common.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID }) + c.setTLSVersion(tls.VersionTLS12, 0, func(suite *tls.CipherSuite) bool { return true }) } func (c *httpClient) RestrictedTLS() { - c.tls.MinVersion = tls.VersionTLS13 - c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool { - return common.Contains(it.SupportedVersions, uint16(tls.VersionTLS13)) - }), func(it *tls.CipherSuite) uint16 { + c.setTLSVersion(tls.VersionTLS13, 0, func(suite *tls.CipherSuite) bool { + return common.Contains(suite.SupportedVersions, uint16(tls.VersionTLS13)) + }) +} + +func (c *httpClient) setTLSVersion(minVersion, maxVersion uint16, filter func(*tls.CipherSuite) bool) { + c.tls.MinVersion = minVersion + if maxVersion != 0 { + c.tls.MaxVersion = maxVersion + } + c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), filter), func(it *tls.CipherSuite) uint16 { return it.ID }) } func (c *httpClient) PinnedTLS12() { - c.tls.MinVersion = tls.VersionTLS12 - c.tls.MaxVersion = tls.VersionTLS12 + c.setTLSVersion(tls.VersionTLS12, tls.VersionTLS12, func(suite *tls.CipherSuite) bool { return true }) } func (c *httpClient) PinnedSHA256(sumHex string) { @@ -178,9 +183,7 @@ func (r *httpRequest) SetUserAgent(userAgent string) { } func (r *httpRequest) SetContent(content []byte) { - buffer := bytes.Buffer{} - buffer.Write(content) - r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes())) + r.request.Body = io.NopCloser(bytes.NewReader(content)) r.request.ContentLength = int64(len(content)) } diff --git a/experimental/libbox/iterator.go b/experimental/libbox/iterator.go index b71ab886..32cbbddb 100644 --- a/experimental/libbox/iterator.go +++ b/experimental/libbox/iterator.go @@ -8,6 +8,12 @@ type StringIterator interface { Next() string } +type Int32Iterator interface { + Len() int32 + HasNext() bool + Next() int32 +} + var _ StringIterator = (*iterator[string])(nil) type iterator[T any] struct { diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 00f63abd..2deedb2e 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -1,7 +1,7 @@ package libbox import ( - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index affcad38..22345201 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -10,7 +10,6 @@ type PlatformInterface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int32) error OpenTun(options TunOptions) (int32, error) - WriteLog(message string) UseProcFS() bool FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) PackageNameByUid(uid int32) (string, error) @@ -26,11 +25,6 @@ type PlatformInterface interface { SendNotification(notification *Notification) error } -type TunInterface interface { - FileDescriptor() int32 - Close() error -} - type InterfaceUpdateListener interface { UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool) } diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go deleted file mode 100644 index d5b6d3c7..00000000 --- a/experimental/libbox/platform/interface.go +++ /dev/null @@ -1,36 +0,0 @@ -package platform - -import ( - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/logger" -) - -type Interface interface { - Initialize(networkManager adapter.NetworkManager) error - UsePlatformAutoDetectInterfaceControl() bool - AutoDetectInterfaceControl(fd int) error - OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) - CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor - Interfaces() ([]adapter.NetworkInterface, error) - UnderNetworkExtension() bool - IncludeAllNetworks() bool - ClearDNSCache() - UsePlatformWIFIMonitor() bool - ReadWIFIState() adapter.WIFIState - SystemCertificates() []string - process.Searcher - SendNotification(notification *Notification) error -} - -type Notification struct { - Identifier string - TypeName string - TypeID int32 - Title string - Subtitle string - Body string - OpenURL string -} diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index c272825f..40c7a149 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -1,123 +1,28 @@ package libbox import ( - "context" + "crypto/rand" + "encoding/hex" + "errors" + "net" "net/netip" - "os" "runtime" - runtimeDebug "runtime/debug" + "strconv" "sync" "syscall" - "time" - "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" - "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "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/pause" ) -type BoxService struct { - ctx context.Context - cancel context.CancelFunc - urlTestHistoryStorage adapter.URLTestHistoryStorage - instance *box.Box - clashServer adapter.ClashServer - pauseManager pause.Manager - - iOSPauseFields -} - -func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - ctx := BaseContext(platformInterface) - service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager)) - options, err := parseConfig(ctx, configContent) - if err != nil { - return nil, err - } - runtimeDebug.FreeOSMemory() - ctx, cancel := context.WithCancel(ctx) - urlTestHistoryStorage := urltest.NewHistoryStorage() - ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - platformWrapper := &platformInterfaceWrapper{ - iif: platformInterface, - useProcFS: platformInterface.UseProcFS(), - } - service.MustRegister[platform.Interface](ctx, platformWrapper) - instance, err := box.New(box.Options{ - Context: ctx, - Options: options, - PlatformLogWriter: platformWrapper, - }) - if err != nil { - cancel() - return nil, E.Cause(err, "create service") - } - runtimeDebug.FreeOSMemory() - return &BoxService{ - ctx: ctx, - cancel: cancel, - instance: instance, - urlTestHistoryStorage: urlTestHistoryStorage, - pauseManager: service.FromContext[pause.Manager](ctx), - clashServer: service.FromContext[adapter.ClashServer](ctx), - }, nil -} - -func (s *BoxService) Start() error { - if sFixAndroidStack { - var err error - done := make(chan struct{}) - go func() { - err = s.instance.Start() - close(done) - }() - <-done - return err - } else { - return s.instance.Start() - } -} - -func (s *BoxService) Close() error { - s.cancel() - s.urlTestHistoryStorage.Close() - var err error - done := make(chan struct{}) - go func() { - err = s.instance.Close() - close(done) - }() - select { - case <-done: - return err - case <-time.After(C.FatalStopTimeout): - os.Exit(1) - return nil - } -} - -func (s *BoxService) NeedWIFIState() bool { - return s.instance.Network().NeedWIFIState() -} - -var ( - _ platform.Interface = (*platformInterfaceWrapper)(nil) - _ log.PlatformWriter = (*platformInterfaceWrapper)(nil) -) +var _ adapter.PlatformInterface = (*platformInterfaceWrapper)(nil) type platformInterfaceWrapper struct { iif PlatformInterface @@ -143,7 +48,11 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error { return w.iif.AutoDetectInterfaceControl(int32(fd)) } -func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { +func (w *platformInterfaceWrapper) UsePlatformInterface() bool { + return true +} + +func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { return nil, E.New("platform: unsupported uid options") } @@ -172,6 +81,10 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return tun.New(*options) } +func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { + return true +} + func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, @@ -179,7 +92,11 @@ func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.L } } -func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) { +func (w *platformInterfaceWrapper) UsePlatformNetworkInterfaces() bool { + return true +} + +func (w *platformInterfaceWrapper) NetworkInterfaces() ([]adapter.NetworkInterface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err @@ -216,7 +133,7 @@ func (w *platformInterfaceWrapper) UnderNetworkExtension() bool { return w.iif.UnderNetworkExtension() } -func (w *platformInterfaceWrapper) IncludeAllNetworks() bool { +func (w *platformInterfaceWrapper) NetworkExtensionIncludeAllNetworks() bool { return w.iif.IncludeAllNetworks() } @@ -224,8 +141,8 @@ func (w *platformInterfaceWrapper) ClearDNSCache() { w.iif.ClearDNSCache() } -func (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool { - return true +func (w *platformInterfaceWrapper) RequestPermissionForWIFIState() error { + return nil } func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { @@ -240,41 +157,82 @@ func (w *platformInterfaceWrapper) SystemCertificates() []string { return iteratorToArray[string](w.iif.SystemCertificates()) } -func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { +func (w *platformInterfaceWrapper) UsePlatformConnectionOwnerFinder() bool { + return true +} + +func (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) { var uid int32 if w.useProcFS { + var source netip.AddrPort + var destination netip.AddrPort + sourceAddr, _ := netip.ParseAddr(request.SourceAddress) + source = netip.AddrPortFrom(sourceAddr, uint16(request.SourcePort)) + destAddr, _ := netip.ParseAddr(request.DestinationAddress) + destination = netip.AddrPortFrom(destAddr, uint16(request.DestinationPort)) + + var network string + switch request.IpProtocol { + case int32(syscall.IPPROTO_TCP): + network = "tcp" + case int32(syscall.IPPROTO_UDP): + network = "udp" + default: + return nil, E.New("unknown protocol: ", request.IpProtocol) + } + uid = procfs.ResolveSocketByProcSearch(network, source, destination) if uid == -1 { return nil, E.New("procfs: not found") } } else { - var ipProtocol int32 - switch N.NetworkName(network) { - case N.NetworkTCP: - ipProtocol = syscall.IPPROTO_TCP - case N.NetworkUDP: - ipProtocol = syscall.IPPROTO_UDP - default: - return nil, E.New("unknown network: ", network) - } var err error - uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port())) + uid, err = w.iif.FindConnectionOwner(request.IpProtocol, request.SourceAddress, request.SourcePort, request.DestinationAddress, request.DestinationPort) if err != nil { return nil, err } } packageName, _ := w.iif.PackageNameByUid(uid) - return &process.Info{UserId: uid, PackageName: packageName}, nil + return &adapter.ConnectionOwner{ + UserId: uid, + AndroidPackageName: packageName, + }, nil } func (w *platformInterfaceWrapper) DisableColors() bool { return runtime.GOOS != "android" } -func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { - w.iif.WriteLog(message) +func (w *platformInterfaceWrapper) UsePlatformNotification() bool { + return true } -func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error { +func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notification) error { return w.iif.SendNotification((*Notification)(notification)) } + +func AvailablePort(startPort int32) (int32, error) { + for port := int(startPort); ; port++ { + if port > 65535 { + return 0, E.New("no available port found") + } + listener, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port)))) + if err != nil { + if errors.Is(err, syscall.EADDRINUSE) { + continue + } + return 0, E.Cause(err, "find available port") + } + err = listener.Close() + if err != nil { + return 0, E.Cause(err, "close listener") + } + return int32(port), nil + } +} + +func RandomHex(length int32) *StringBox { + bytes := make([]byte, length) + common.Must1(rand.Read(bytes)) + return wrapString(hex.EncodeToString(bytes)) +} diff --git a/experimental/libbox/service_error.go b/experimental/libbox/service_error.go deleted file mode 100644 index bb0593bf..00000000 --- a/experimental/libbox/service_error.go +++ /dev/null @@ -1,32 +0,0 @@ -package libbox - -import ( - "os" - "path/filepath" -) - -func serviceErrorPath() string { - return filepath.Join(sWorkingPath, "network_extension_error") -} - -func ClearServiceError() { - os.Remove(serviceErrorPath()) -} - -func ReadServiceError() (*StringBox, error) { - data, err := os.ReadFile(serviceErrorPath()) - if err == nil { - os.Remove(serviceErrorPath()) - } - return wrapString(string(data)), err -} - -func WriteServiceError(message string) error { - errorFile, err := os.Create(serviceErrorPath()) - if err != nil { - return err - } - errorFile.WriteString(message) - errorFile.Chown(sUserID, sGroupID) - return errorFile.Close() -} diff --git a/experimental/libbox/service_pause.go b/experimental/libbox/service_pause.go deleted file mode 100644 index 9c888454..00000000 --- a/experimental/libbox/service_pause.go +++ /dev/null @@ -1,36 +0,0 @@ -package libbox - -import ( - "time" - - C "github.com/sagernet/sing-box/constant" -) - -type iOSPauseFields struct { - endPauseTimer *time.Timer -} - -func (s *BoxService) Pause() { - s.pauseManager.DevicePause() - if C.IsIos { - if s.endPauseTimer == nil { - s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake) - } else { - s.endPauseTimer.Reset(time.Minute) - } - } -} - -func (s *BoxService) Wake() { - if !C.IsIos { - s.pauseManager.DeviceWake() - } -} - -func (s *BoxService) ResetNetwork() { - s.instance.Router().ResetNetwork() -} - -func (s *BoxService) UpdateWIFIState() { - s.instance.Network().UpdateWIFIState() -} diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index ad898fee..b4b6ee8e 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -2,9 +2,7 @@ package libbox import ( "os" - "os/user" "runtime/debug" - "strconv" "time" C "github.com/sagernet/sing-box/constant" @@ -14,55 +12,53 @@ import ( ) var ( - sBasePath string - sWorkingPath string - sTempPath string - sUserID int - sGroupID int - sTVOS bool - sFixAndroidStack bool + sBasePath string + sWorkingPath string + sTempPath string + sUserID int + sGroupID int + sFixAndroidStack bool + sCommandServerListenPort uint16 + sCommandServerSecret string + sLogMaxLines int + sDebug bool ) func init() { debug.SetPanicOnFault(true) + debug.SetTraceback("all") } type SetupOptions struct { - BasePath string - WorkingPath string - TempPath string - Username string - IsTVOS bool - FixAndroidStack bool + BasePath string + WorkingPath string + TempPath string + FixAndroidStack bool + CommandServerListenPort int32 + CommandServerSecret string + LogMaxLines int + Debug bool } func Setup(options *SetupOptions) error { sBasePath = options.BasePath sWorkingPath = options.WorkingPath sTempPath = options.TempPath - if options.Username != "" { - sUser, err := user.Lookup(options.Username) - if err != nil { - return err - } - sUserID, _ = strconv.Atoi(sUser.Uid) - sGroupID, _ = strconv.Atoi(sUser.Gid) - } else { - sUserID = os.Getuid() - sGroupID = os.Getgid() - } - sTVOS = options.IsTVOS + + sUserID = os.Getuid() + sGroupID = os.Getgid() // TODO: remove after fixed // https://github.com/golang/go/issues/68760 sFixAndroidStack = options.FixAndroidStack + sCommandServerListenPort = uint16(options.CommandServerListenPort) + sCommandServerSecret = options.CommandServerSecret + sLogMaxLines = options.LogMaxLines + sDebug = options.Debug + os.MkdirAll(sWorkingPath, 0o777) os.MkdirAll(sTempPath, 0o777) - if options.Username != "" { - os.Chown(sWorkingPath, sUserID, sGroupID) - os.Chown(sTempPath, sUserID, sGroupID) - } return nil } diff --git a/experimental/libbox/tun.go b/experimental/libbox/tun.go index 18b7910e..84c6372a 100644 --- a/experimental/libbox/tun.go +++ b/experimental/libbox/tun.go @@ -5,7 +5,7 @@ import ( "net/netip" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" + tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" ) diff --git a/log/observable.go b/log/observable.go index eea1df84..768942bd 100644 --- a/log/observable.go +++ b/log/observable.go @@ -50,9 +50,9 @@ func NewDefaultFactory( level: LevelTrace, subscriber: observable.NewSubscriber[Entry](128), } - if platformWriter != nil { + /*if platformWriter != nil { factory.platformFormatter.DisableColors = platformWriter.DisableColors() - } + }*/ if needObservable { factory.observer = observable.NewObserver[Entry](factory.subscriber, 64) } @@ -111,28 +111,30 @@ type observableLogger struct { func (l *observableLogger) Log(ctx context.Context, level Level, args []any) { level = OverrideLevelFromContext(level, ctx) - if level > l.level { + if level > l.level && l.platformWriter == nil { return } nowTime := time.Now() - if l.needObservable { - message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime) - if level == LevelPanic { - panic(message) - } - l.writer.Write([]byte(message)) - if level == LevelFatal { - os.Exit(1) - } - l.subscriber.Emit(Entry{level, messageSimple}) - } else { - message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime) - if level == LevelPanic { - panic(message) - } - l.writer.Write([]byte(message)) - if level == LevelFatal { - os.Exit(1) + if level <= l.level { + if l.needObservable { + message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime) + if level == LevelPanic { + panic(message) + } + l.writer.Write([]byte(message)) + if level == LevelFatal { + os.Exit(1) + } + l.subscriber.Emit(Entry{level, messageSimple}) + } else { + message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime) + if level == LevelPanic { + panic(message) + } + l.writer.Write([]byte(message)) + if level == LevelFatal { + os.Exit(1) + } } } if l.platformWriter != nil { diff --git a/log/platform.go b/log/platform.go index c6a9e525..a8881d4c 100644 --- a/log/platform.go +++ b/log/platform.go @@ -1,6 +1,5 @@ package log type PlatformWriter interface { - DisableColors() bool WriteMessage(level Level, message string) } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index ab979088..e7e4f925 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -28,7 +28,6 @@ import ( "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/route/rule" @@ -80,7 +79,7 @@ type Endpoint struct { logger logger.ContextLogger dnsRouter adapter.DNSRouter network adapter.NetworkManager - platformInterface platform.Interface + platformInterface adapter.PlatformInterface server *tsnet.Server stack *stack.Stack icmpForwarder *tun.ICMPForwarder @@ -190,7 +189,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL logger: logger, dnsRouter: dnsRouter, network: service.FromContext[adapter.NetworkManager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), server: server, acceptRoutes: options.AcceptRoutes, exitNode: options.ExitNode, @@ -290,7 +289,7 @@ func (t *Endpoint) watchState() { if authURL != "" { t.logger.Info("Waiting for authentication: ", authURL) if t.platformInterface != nil { - err := t.platformInterface.SendNotification(&platform.Notification{ + err := t.platformInterface.SendNotification(&adapter.Notification{ Identifier: "tailscale-authentication", TypeName: "Tailscale Authentication Notifications", TypeID: 10, diff --git a/protocol/tailscale/protect_android.go b/protocol/tailscale/protect_android.go index 90ab615a..37dd33bd 100644 --- a/protocol/tailscale/protect_android.go +++ b/protocol/tailscale/protect_android.go @@ -1,11 +1,11 @@ package tailscale import ( - "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/tailscale/net/netns" ) -func setAndroidProtectFunc(platformInterface platform.Interface) { +func setAndroidProtectFunc(platformInterface adapter.PlatformInterface) { if platformInterface != nil { netns.SetAndroidProtectFunc(func(fd int) error { return platformInterface.AutoDetectInterfaceControl(fd) diff --git a/protocol/tailscale/protect_nonandroid.go b/protocol/tailscale/protect_nonandroid.go index eeb56bf6..f315c2ea 100644 --- a/protocol/tailscale/protect_nonandroid.go +++ b/protocol/tailscale/protect_nonandroid.go @@ -2,7 +2,7 @@ package tailscale -import "github.com/sagernet/sing-box/experimental/libbox/platform" +import "github.com/sagernet/sing-box/adapter" -func setAndroidProtectFunc(platformInterface platform.Interface) { +func setAndroidProtectFunc(platformInterface adapter.PlatformInterface) { } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 20b1cd2f..1cb2f309 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/route/rule" @@ -49,7 +48,7 @@ type Inbound struct { stack string tunIf tun.Tun tunStack tun.Stack - platformInterface platform.Interface + platformInterface adapter.PlatformInterface platformOptions option.TunPlatformOptions autoRedirect tun.AutoRedirect routeRuleSet []adapter.RuleSet @@ -131,7 +130,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo deprecated.Report(ctx, deprecated.OptionTUNGSO) } - platformInterface := service.FromContext[platform.Interface](ctx) + platformInterface := service.FromContext[adapter.PlatformInterface](ctx) tunMTU := options.MTU enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152 if tunMTU == 0 { @@ -373,8 +372,8 @@ func (t *Inbound) Start(stage adapter.StartStage) error { } } monitor.Start("open interface") - if t.platformInterface != nil { - tunInterface, err = t.platformInterface.OpenTun(&tunOptions, t.platformOptions) + if t.platformInterface != nil && t.platformInterface.UsePlatformInterface() { + tunInterface, err = t.platformInterface.OpenInterface(&tunOptions, t.platformOptions) } else { if HookBeforeCreatePlatformInterface != nil { HookBeforeCreatePlatformInterface() @@ -394,7 +393,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error { ) if t.platformInterface != nil { forwarderBindInterface = true - includeAllNetworks = t.platformInterface.IncludeAllNetworks() + includeAllNetworks = t.platformInterface.NetworkExtensionIncludeAllNetworks() } tunStack, err := tun.NewStack(t.stack, tun.StackOptions{ Context: t.ctx, diff --git a/route/network.go b/route/network.go index 52d74c8c..3c041766 100644 --- a/route/network.go +++ b/route/network.go @@ -17,7 +17,6 @@ import ( "github.com/sagernet/sing-box/common/settings" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" @@ -48,7 +47,7 @@ type NetworkManager struct { packageManager tun.PackageManager powerListener winpowrprof.EventListener pauseManager pause.Manager - platformInterface platform.Interface + platformInterface adapter.PlatformInterface endpoint adapter.EndpointManager inbound adapter.InboundManager outbound adapter.OutboundManager @@ -90,7 +89,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp FallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), }, pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), endpoint: service.FromContext[adapter.EndpointManager](ctx), inbound: service.FromContext[adapter.InboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx), @@ -189,7 +188,7 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { } } case adapter.StartStatePostStart: - if r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) { + if r.needWIFIState && r.platformInterface == nil { wifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged) if err != nil { if err != os.ErrInvalid { @@ -264,10 +263,10 @@ func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { } func (r *NetworkManager) UpdateInterfaces() error { - if r.platformInterface == nil { + if r.platformInterface == nil || !r.platformInterface.UsePlatformNetworkInterfaces() { return r.interfaceFinder.Update() } else { - interfaces, err := r.platformInterface.Interfaces() + interfaces, err := r.platformInterface.NetworkInterfaces() if err != nil { return err } @@ -440,7 +439,7 @@ func (r *NetworkManager) UpdateWIFIState() { var state adapter.WIFIState if r.wifiMonitor != nil { state = r.wifiMonitor.ReadWIFIState() - } else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() { + } else if r.platformInterface != nil { state = r.platformInterface.ReadWIFIState() } else { return diff --git a/route/platform_searcher.go b/route/platform_searcher.go new file mode 100644 index 00000000..f6a4e764 --- /dev/null +++ b/route/platform_searcher.go @@ -0,0 +1,45 @@ +package route + +import ( + "context" + "net/netip" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/process" + N "github.com/sagernet/sing/common/network" +) + +type platformSearcher struct { + platform adapter.PlatformInterface +} + +func newPlatformSearcher(platform adapter.PlatformInterface) process.Searcher { + return &platformSearcher{platform: platform} +} + +func (s *platformSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { + if !s.platform.UsePlatformConnectionOwnerFinder() { + return nil, process.ErrNotFound + } + + var ipProtocol int32 + switch N.NetworkName(network) { + case N.NetworkTCP: + ipProtocol = syscall.IPPROTO_TCP + case N.NetworkUDP: + ipProtocol = syscall.IPPROTO_UDP + default: + return nil, process.ErrNotFound + } + + request := &adapter.FindConnectionOwnerRequest{ + IpProtocol: ipProtocol, + SourceAddress: source.Addr().String(), + SourcePort: int32(source.Port()), + DestinationAddress: destination.Addr().String(), + DestinationPort: int32(destination.Port()), + } + + return s.platform.FindConnectionOwner(request) +} diff --git a/route/route.go b/route/route.go index 1d6663d8..2f1d01f7 100644 --- a/route/route.go +++ b/route/route.go @@ -382,18 +382,18 @@ func (r *Router) matchRule( r.logger.InfoContext(ctx, "failed to search process: ", fErr) } else { if processInfo.ProcessPath != "" { - if processInfo.User != "" { - r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.User) + if processInfo.UserName != "" { + r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.UserName) } else if processInfo.UserId != -1 { r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user id: ", processInfo.UserId) } else { r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) } - } else if processInfo.PackageName != "" { - r.logger.InfoContext(ctx, "found package name: ", processInfo.PackageName) + } else if processInfo.AndroidPackageName != "" { + r.logger.InfoContext(ctx, "found package name: ", processInfo.AndroidPackageName) } else if processInfo.UserId != -1 { - if processInfo.User != "" { - r.logger.InfoContext(ctx, "found user: ", processInfo.User) + if processInfo.UserName != "" { + r.logger.InfoContext(ctx, "found user: ", processInfo.UserName) } else { r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) } diff --git a/route/router.go b/route/router.go index aca65432..e3802dd8 100644 --- a/route/router.go +++ b/route/router.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" @@ -37,7 +36,7 @@ type Router struct { processSearcher process.Searcher pauseManager pause.Manager trackers []adapter.ConnectionTracker - platformInterface platform.Interface + platformInterface adapter.PlatformInterface started bool } @@ -55,7 +54,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route ruleSetMap: make(map[string]adapter.RuleSet), needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess, pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: service.FromContext[platform.Interface](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), } } @@ -120,8 +119,8 @@ func (r *Router) Start(stage adapter.StartStage) error { } } if needFindProcess { - if r.platformInterface != nil { - r.processSearcher = r.platformInterface + if r.platformInterface != nil && r.platformInterface.UsePlatformConnectionOwnerFinder() { + r.processSearcher = newPlatformSearcher(r.platformInterface) } else { monitor.Start("initialize process searcher") searcher, err := process.NewSearcher(process.Config{ diff --git a/route/rule/rule_item_package_name.go b/route/rule/rule_item_package_name.go index 0066735c..fa227587 100644 --- a/route/rule/rule_item_package_name.go +++ b/route/rule/rule_item_package_name.go @@ -25,10 +25,10 @@ func NewPackageNameItem(packageNameList []string) *PackageNameItem { } func (r *PackageNameItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.PackageName == "" { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.AndroidPackageName == "" { return false } - return r.packageMap[metadata.ProcessInfo.PackageName] + return r.packageMap[metadata.ProcessInfo.AndroidPackageName] } func (r *PackageNameItem) String() string { diff --git a/route/rule/rule_item_user.go b/route/rule/rule_item_user.go index d635fa16..87a8bff1 100644 --- a/route/rule/rule_item_user.go +++ b/route/rule/rule_item_user.go @@ -26,10 +26,10 @@ func NewUserItem(users []string) *UserItem { } func (r *UserItem) Match(metadata *adapter.InboundContext) bool { - if metadata.ProcessInfo == nil || metadata.ProcessInfo.User == "" { + if metadata.ProcessInfo == nil || metadata.ProcessInfo.UserName == "" { return false } - return r.userMap[metadata.ProcessInfo.User] + return r.userMap[metadata.ProcessInfo.UserName] } func (r *UserItem) String() string { diff --git a/service/resolved/resolve1.go b/service/resolved/resolve1.go index 8e6dd3fa..ed1ee41a 100644 --- a/service/resolved/resolve1.go +++ b/service/resolved/resolve1.go @@ -15,7 +15,6 @@ import ( "syscall" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/process" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/log" @@ -111,7 +110,7 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont if err != nil { return metadata } - var processInfo process.Info + var processInfo adapter.ConnectionOwner metadata.ProcessInfo = &processInfo processInfo.ProcessID = uint32(senderPid) @@ -140,7 +139,7 @@ func (t *resolve1Manager) createMetadata(sender dbus.Sender) adapter.InboundCont processInfo.UserId = int32(uid) uidFound = true if osUser, _ := user.LookupId(F.ToString(uid)); osUser != nil { - processInfo.User = osUser.Username + processInfo.UserName = osUser.Username } break } @@ -159,8 +158,8 @@ func (t *resolve1Manager) log(sender dbus.Sender, message ...any) { var prefix string if metadata.ProcessInfo.ProcessPath != "" { prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) - } else if metadata.ProcessInfo.User != "" { - prefix = F.ToString("user:", metadata.ProcessInfo.User) + } else if metadata.ProcessInfo.UserName != "" { + prefix = F.ToString("user:", metadata.ProcessInfo.UserName) } else if metadata.ProcessInfo.UserId != 0 { prefix = F.ToString("uid:", metadata.ProcessInfo.UserId) } @@ -177,8 +176,8 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context var prefix string if metadata.ProcessInfo.ProcessPath != "" { prefix = filepath.Base(metadata.ProcessInfo.ProcessPath) - } else if metadata.ProcessInfo.User != "" { - prefix = F.ToString("user:", metadata.ProcessInfo.User) + } else if metadata.ProcessInfo.UserName != "" { + prefix = F.ToString("user:", metadata.ProcessInfo.UserName) } else if metadata.ProcessInfo.UserId != 0 { prefix = F.ToString("uid:", metadata.ProcessInfo.UserId) } From a930356b04cbcacaa5cb9a397e0f8aea340c0f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 13 Oct 2025 13:31:44 +0800 Subject: [PATCH 045/185] Revert "Stop using DHCP on iOS and tvOS" --- cmd/internal/build_libbox/main.go | 10 ++++----- constant/dhcp.go | 2 +- dns/transport/dhcp/dhcp.go | 25 ++++++++++++++++------ dns/transport/local/local_darwin.go | 32 +++++++++++------------------ 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 3400473c..045d3dc5 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -46,7 +46,7 @@ var ( sharedFlags []string debugFlags []string sharedTags []string - macOSTags []string + darwinTags []string memcTags []string notMemcTags []string debugTags []string @@ -63,7 +63,7 @@ func init() { debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0") - macOSTags = append(macOSTags, "with_dhcp") + darwinTags = append(darwinTags, "with_dhcp") memcTags = append(memcTags, "with_tailscale") notMemcTags = append(notMemcTags, "with_low_memory") debugTags = append(debugTags, "debug") @@ -158,9 +158,7 @@ func buildApple() { "-tags-not-macos=with_low_memory", } if !withTailscale { - args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ",")) - } else { - args = append(args, "-tags-macos="+strings.Join(macOSTags, ",")) + args = append(args, "-tags-macos="+strings.Join(memcTags, ",")) } if !debugEnabled { @@ -169,7 +167,7 @@ func buildApple() { args = append(args, debugFlags...) } - tags := sharedTags + tags := append(sharedTags, darwinTags...) if withTailscale { tags = append(tags, memcTags...) } diff --git a/constant/dhcp.go b/constant/dhcp.go index 1d9792a2..bdabd06e 100644 --- a/constant/dhcp.go +++ b/constant/dhcp.go @@ -4,5 +4,5 @@ import "time" const ( DHCPTTL = time.Hour - DHCPTimeout = time.Minute + DHCPTimeout = 5 * time.Second ) diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go index d25b081f..3f13d1d9 100644 --- a/dns/transport/dhcp/dhcp.go +++ b/dns/transport/dhcp/dhcp.go @@ -49,6 +49,7 @@ type Transport struct { interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] transportLock sync.RWMutex updatedAt time.Time + lastError error servers []M.Socksaddr search []string ndots int @@ -92,7 +93,7 @@ func (t *Transport) Start(stage adapter.StartStage) error { t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated) } go func() { - _, err := t.Fetch() + _, err := t.fetch() if err != nil { t.logger.Error(E.Cause(err, "fetch DNS servers")) } @@ -108,7 +109,7 @@ func (t *Transport) Close() error { } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - servers, err := t.Fetch() + servers, err := t.fetch() if err != nil { return nil, err } @@ -128,11 +129,20 @@ func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers [] } } -func (t *Transport) Fetch() ([]M.Socksaddr, error) { +func (t *Transport) Fetch() []M.Socksaddr { + servers, _ := t.fetch() + return servers +} + +func (t *Transport) fetch() ([]M.Socksaddr, error) { t.transportLock.RLock() updatedAt := t.updatedAt + lastError := t.lastError servers := t.servers t.transportLock.RUnlock() + if lastError != nil { + return nil, lastError + } if time.Since(updatedAt) < C.DHCPTTL { return servers, nil } @@ -143,7 +153,7 @@ func (t *Transport) Fetch() ([]M.Socksaddr, error) { } err := t.updateServers() if err != nil { - return nil, err + return servers, err } return t.servers, nil } @@ -173,12 +183,15 @@ func (t *Transport) updateServers() error { fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout) err = t.fetchServers0(fetchCtx, iface) cancel() + t.updatedAt = time.Now() if err != nil { + t.lastError = err return err } else if len(t.servers) == 0 { - return E.New("dhcp: empty DNS servers response") + t.lastError = E.New("dhcp: empty DNS servers response") + return t.lastError } else { - t.updatedAt = time.Now() + t.lastError = nil return nil } } diff --git a/dns/transport/local/local_darwin.go b/dns/transport/local/local_darwin.go index 3f2b424c..ee759b91 100644 --- a/dns/transport/local/local_darwin.go +++ b/dns/transport/local/local_darwin.go @@ -43,7 +43,7 @@ type Transport struct { type dhcpTransport interface { adapter.DNSTransport - Fetch() ([]M.Socksaddr, error) + Fetch() []M.Socksaddr Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) } @@ -74,14 +74,12 @@ func (t *Transport) Start(stage adapter.StartStage) error { break } } - if !C.IsIos { - if t.fallback { - t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger) - if t.dhcpTransport != nil { - err := t.dhcpTransport.Start(stage) - if err != nil { - return err - } + if t.fallback { + t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger) + if t.dhcpTransport != nil { + err := t.dhcpTransport.Start(stage) + if err != nil { + return err } } } @@ -105,12 +103,10 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, if !t.fallback { return t.exchange(ctx, message, question.Name) } - if !C.IsIos { - if t.dhcpTransport != nil { - dhcpTransports, _ := t.dhcpTransport.Fetch() - if len(dhcpTransports) > 0 { - return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports) - } + if t.dhcpTransport != nil { + dhcpTransports := t.dhcpTransport.Fetch() + if len(dhcpTransports) > 0 { + return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports) } } if t.preferGo { @@ -134,9 +130,5 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, } return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil } - if C.IsIos { - return nil, E.New("only A and AAAA queries are supported on iOS and tvOS when using NetworkExtension.") - } else { - return nil, E.New("only A and AAAA queries are supported on macOS when using NetworkExtension and DHCP unavailable.") - } + return nil, E.New("only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.") } From a5fb467db2b63214274840763468502a0c088b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 22 Oct 2025 23:26:05 +0800 Subject: [PATCH 046/185] daemon: Add clear logs --- daemon/started_service.go | 5 ++ daemon/started_service.pb.go | 85 ++++++++++++++------------- daemon/started_service.proto | 1 + daemon/started_service_grpc.pb.go | 39 ++++++++++++ experimental/libbox/command_client.go | 10 ++++ experimental/libbox/command_server.go | 2 +- 6 files changed, 100 insertions(+), 42 deletions(-) diff --git a/daemon/started_service.go b/daemon/started_service.go index 8b926b27..acbbc503 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -351,6 +351,11 @@ func (s *StartedService) GetDefaultLogLevel(ctx context.Context, empty *emptypb. return &DefaultLogLevel{Level: LogLevel(logLevel)}, nil } +func (s *StartedService) ClearLogs(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.resetLogs() + return &emptypb.Empty{}, nil +} + func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server grpc.ServerStreamingServer[Status]) error { interval := time.Duration(request.Interval) if interval <= 0 { diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go index 90cff998..dcd94feb 100644 --- a/daemon/started_service.pb.go +++ b/daemon/started_service.pb.go @@ -1746,13 +1746,14 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x10ConnectionSortBy\x12\b\n" + "\x04DATE\x10\x00\x12\v\n" + "\aTRAFFIC\x10\x01\x12\x11\n" + - "\rTOTAL_TRAFFIC\x10\x022\xf8\v\n" + + "\rTOTAL_TRAFFIC\x10\x022\xb7\f\n" + "\x0eStartedService\x12=\n" + "\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" + "\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" + "\x16SubscribeServiceStatus\x12\x16.google.protobuf.Empty\x1a\x15.daemon.ServiceStatus\"\x000\x01\x127\n" + "\fSubscribeLog\x12\x16.google.protobuf.Empty\x1a\v.daemon.Log\"\x000\x01\x12G\n" + - "\x12GetDefaultLogLevel\x12\x16.google.protobuf.Empty\x1a\x17.daemon.DefaultLogLevel\"\x00\x12E\n" + + "\x12GetDefaultLogLevel\x12\x16.google.protobuf.Empty\x1a\x17.daemon.DefaultLogLevel\"\x00\x12=\n" + + "\tClearLogs\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12E\n" + "\x0fSubscribeStatus\x12\x1e.daemon.SubscribeStatusRequest\x1a\x0e.daemon.Status\"\x000\x01\x12=\n" + "\x0fSubscribeGroups\x12\x16.google.protobuf.Empty\x1a\x0e.daemon.Groups\"\x000\x01\x12G\n" + "\x12GetClashModeStatus\x12\x16.google.protobuf.Empty\x1a\x17.daemon.ClashModeStatus\"\x00\x12C\n" + @@ -1835,45 +1836,47 @@ var file_daemon_started_service_proto_depIdxs = []int32{ 27, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty 27, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty 27, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty - 6, // 15: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest - 27, // 16: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty - 27, // 17: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty - 27, // 18: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty - 16, // 19: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode - 13, // 20: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest - 14, // 21: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest - 15, // 22: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest - 27, // 23: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty - 19, // 24: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest - 20, // 25: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest - 23, // 26: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest - 27, // 27: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty - 27, // 28: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty - 27, // 29: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty - 28, // 30: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse - 27, // 31: daemon.StartedService.StopService:output_type -> google.protobuf.Empty - 27, // 32: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty - 4, // 33: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus - 7, // 34: daemon.StartedService.SubscribeLog:output_type -> daemon.Log - 8, // 35: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel - 9, // 36: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status - 10, // 37: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups - 17, // 38: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus - 16, // 39: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode - 27, // 40: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty - 27, // 41: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty - 27, // 42: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty - 27, // 43: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty - 18, // 44: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus - 27, // 45: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty - 21, // 46: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections - 27, // 47: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty - 27, // 48: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty - 24, // 49: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings - 29, // 50: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest - 27, // 51: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty - 31, // [31:52] is the sub-list for method output_type - 10, // [10:31] is the sub-list for method input_type + 27, // 15: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty + 6, // 16: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest + 27, // 17: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty + 27, // 18: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty + 27, // 19: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty + 16, // 20: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode + 13, // 21: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest + 14, // 22: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest + 15, // 23: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest + 27, // 24: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty + 19, // 25: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest + 20, // 26: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest + 23, // 27: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest + 27, // 28: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty + 27, // 29: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty + 27, // 30: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty + 28, // 31: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse + 27, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty + 27, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty + 4, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 7, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 8, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 27, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty + 9, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 10, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 17, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 16, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 27, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 27, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 27, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 27, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 18, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 27, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty + 21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections + 27, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 27, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 24, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 29, // 52: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest + 27, // 53: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty + 32, // [32:54] is the sub-list for method output_type + 10, // [10:32] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name diff --git a/daemon/started_service.proto b/daemon/started_service.proto index 17e9f683..83b72ff8 100644 --- a/daemon/started_service.proto +++ b/daemon/started_service.proto @@ -13,6 +13,7 @@ service StartedService { rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {} rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {} rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {} + rpc ClearLogs(google.protobuf.Empty) returns(google.protobuf.Empty) {} rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {} rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {} diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go index 4807b25c..dec45dae 100644 --- a/daemon/started_service_grpc.pb.go +++ b/daemon/started_service_grpc.pb.go @@ -20,6 +20,7 @@ const ( StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus" StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog" StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel" + StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs" StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus" StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups" StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus" @@ -47,6 +48,7 @@ type StartedServiceClient interface { SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) + ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) @@ -141,6 +143,16 @@ func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *empty return out, nil } +func (c *startedServiceClient) ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, StartedService_ClearLogs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...) @@ -355,6 +367,7 @@ type StartedServiceServer interface { SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) + ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) @@ -401,6 +414,10 @@ func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *em return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented") } +func (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClearLogs not implemented") +} + func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error { return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented") } @@ -561,6 +578,24 @@ func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _StartedService_ClearLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).ClearLogs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_ClearLogs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).ClearLogs(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(SubscribeStatusRequest) if err := stream.RecvMsg(m); err != nil { @@ -833,6 +868,10 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetDefaultLogLevel", Handler: _StartedService_GetDefaultLogLevel_Handler, }, + { + MethodName: "ClearLogs", + Handler: _StartedService_ClearLogs_Handler, + }, { MethodName: "GetClashModeStatus", Handler: _StartedService_GetClashModeStatus_Handler, diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index f5f6c6e2..9885af6e 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -410,6 +410,16 @@ func (c *CommandClient) ServiceClose() error { return err } +func (c *CommandClient) ClearLogs() error { + client, err := c.getClientForCall() + if err != nil { + return err + } + + _, err = client.ClearLogs(context.Background(), &emptypb.Empty{}) + return err +} + func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { client, err := c.getClientForCall() if err != nil { diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 5195c0b1..b25f3065 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -192,7 +192,7 @@ func (s *CommandServer) NeedWIFIState() bool { if instance == nil || instance.Box() == nil { return false } - return instance.Box().Router().NeedWIFIState() + return instance.Box().Network().NeedWIFIState() } func (s *CommandServer) Pause() { From ac427b98f4a15f4598181d513346c1b4e3a0c524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 7 Dec 2025 11:24:45 +0800 Subject: [PATCH 047/185] platform: Add UsePlatformWIFIMonitor to gRPC interface Align dev-next-grpc with wip2 by adding UsePlatformWIFIMonitor() to the new PlatformInterface, allowing platform clients to indicate they handle WIFI monitoring themselves. --- adapter/platform.go | 2 ++ experimental/libbox/config.go | 4 +++ experimental/libbox/service.go | 4 +++ route/network.go | 58 ++++++++++++++++++---------------- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/adapter/platform.go b/adapter/platform.go index 61dc7b44..95db93c6 100644 --- a/adapter/platform.go +++ b/adapter/platform.go @@ -32,6 +32,8 @@ type PlatformInterface interface { UsePlatformConnectionOwnerFinder() bool FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error) + UsePlatformWIFIMonitor() bool + UsePlatformNotification() bool SendNotification(notification *Notification) error } diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 946eea26..c89f3d69 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -116,6 +116,10 @@ func (s *platformInterfaceStub) RequestPermissionForWIFIState() error { return nil } +func (s *platformInterfaceStub) UsePlatformWIFIMonitor() bool { + return false +} + func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { return adapter.WIFIState{} } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 40c7a149..2cc270f4 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -145,6 +145,10 @@ func (w *platformInterfaceWrapper) RequestPermissionForWIFIState() error { return nil } +func (w *platformInterfaceWrapper) UsePlatformWIFIMonitor() bool { + return true +} + func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { wifiState := w.iif.ReadWIFIState() if wifiState == nil { diff --git a/route/network.go b/route/network.go index 3c041766..b53142b5 100644 --- a/route/network.go +++ b/route/network.go @@ -58,24 +58,24 @@ type NetworkManager struct { started bool } -func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) { - defaultDomainResolver := common.PtrValueOrDefault(routeOptions.DefaultDomainResolver) - if routeOptions.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) { +func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) { + defaultDomainResolver := common.PtrValueOrDefault(options.DefaultDomainResolver) + if options.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) { return nil, E.New("`auto_detect_interface` is only supported on Linux, Windows and macOS") - } else if routeOptions.OverrideAndroidVPN && !C.IsAndroid { + } else if options.OverrideAndroidVPN && !C.IsAndroid { return nil, E.New("`override_android_vpn` is only supported on Android") - } else if routeOptions.DefaultInterface != "" && !(C.IsLinux || C.IsDarwin || C.IsWindows) { + } else if options.DefaultInterface != "" && !(C.IsLinux || C.IsDarwin || C.IsWindows) { return nil, E.New("`default_interface` is only supported on Linux, Windows and macOS") - } else if routeOptions.DefaultMark != 0 && !C.IsLinux { + } else if options.DefaultMark != 0 && !C.IsLinux { return nil, E.New("`default_mark` is only supported on linux") } nm := &NetworkManager{ logger: logger, interfaceFinder: control.NewDefaultInterfaceFinder(), - autoDetectInterface: routeOptions.AutoDetectInterface, + autoDetectInterface: options.AutoDetectInterface, defaultOptions: adapter.NetworkOptions{ - BindInterface: routeOptions.DefaultInterface, - RoutingMark: uint32(routeOptions.DefaultMark), + BindInterface: options.DefaultInterface, + RoutingMark: uint32(options.DefaultMark), DomainResolver: defaultDomainResolver.Server, DomainResolveOptions: adapter.DNSQueryOptions{ Strategy: C.DomainStrategy(defaultDomainResolver.Strategy), @@ -83,28 +83,28 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp RewriteTTL: defaultDomainResolver.RewriteTTL, ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}), }, - NetworkStrategy: (*C.NetworkStrategy)(routeOptions.DefaultNetworkStrategy), - NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build), - FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build), - FallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), + NetworkStrategy: (*C.NetworkStrategy)(options.DefaultNetworkStrategy), + NetworkType: common.Map(options.DefaultNetworkType, option.InterfaceType.Build), + FallbackNetworkType: common.Map(options.DefaultFallbackNetworkType, option.InterfaceType.Build), + FallbackDelay: time.Duration(options.DefaultFallbackDelay), }, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[adapter.PlatformInterface](ctx), endpoint: service.FromContext[adapter.EndpointManager](ctx), inbound: service.FromContext[adapter.InboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx), - needWIFIState: hasRule(routeOptions.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), + needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } - if routeOptions.DefaultNetworkStrategy != nil { - if routeOptions.DefaultInterface != "" { + if options.DefaultNetworkStrategy != nil { + if options.DefaultInterface != "" { return nil, E.New("`default_network_strategy` is conflict with `default_interface`") } - if !routeOptions.AutoDetectInterface { + if !options.AutoDetectInterface { return nil, E.New("`auto_detect_interface` is required by `default_network_strategy`") } } usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil - enforceInterfaceMonitor := routeOptions.AutoDetectInterface + enforceInterfaceMonitor := options.AutoDetectInterface if !usePlatformDefaultInterfaceMonitor { networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { @@ -114,7 +114,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp nm.networkMonitor = networkMonitor interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(nm.networkMonitor, logger, tun.DefaultInterfaceMonitorOptions{ InterfaceFinder: nm.interfaceFinder, - OverrideAndroidVPN: routeOptions.OverrideAndroidVPN, + OverrideAndroidVPN: options.OverrideAndroidVPN, UnderNetworkExtension: nm.platformInterface != nil && nm.platformInterface.UnderNetworkExtension(), }) if err != nil { @@ -188,7 +188,7 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { } } case adapter.StartStatePostStart: - if r.needWIFIState && r.platformInterface == nil { + if r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) { wifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged) if err != nil { if err != os.ErrInvalid { @@ -424,14 +424,16 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState { func (r *NetworkManager) onWIFIStateChanged(state adapter.WIFIState) { r.wifiStateMutex.Lock() - if state == r.wifiState { + if state != r.wifiState { + r.wifiState = state + r.wifiStateMutex.Unlock() + if state.SSID != "" { + r.logger.Info("WIFI state changed: SSID=", state.SSID, ", BSSID=", state.BSSID) + } else { + r.logger.Info("WIFI disconnected") + } + } else { r.wifiStateMutex.Unlock() - return - } - r.wifiState = state - r.wifiStateMutex.Unlock() - if state.SSID != "" { - r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) } } @@ -439,7 +441,7 @@ func (r *NetworkManager) UpdateWIFIState() { var state adapter.WIFIState if r.wifiMonitor != nil { state = r.wifiMonitor.ReadWIFIState() - } else if r.platformInterface != nil { + } else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() { state = r.platformInterface.ReadWIFIState() } else { return From 7e68013b0587cd4dcb37c100d1d73376e15a53a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 13 Dec 2025 16:04:34 +0800 Subject: [PATCH 048/185] Apply ping destination filter for Windows --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b3728906..7be6b685 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.11 + github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 diff --git a/go.sum b/go.sum index d572feec..0d6d29d1 100644 --- a/go.sum +++ b/go.sum @@ -173,8 +173,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.11 h1:xVi8VcVkvz2o+3v1PLv5MOkFpiVCwjLjucVlmigDi5c= -github.com/sagernet/sing-tun v0.8.0-beta.11/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e h1:ZEv+9vy7vC1vbr3LfwZGx3JAOkl/w4+hnGamHw4W36M= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From ad7b9822420a86aac55b7b13e62494b344620c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 12 Dec 2025 15:02:08 +0800 Subject: [PATCH 049/185] Add naiveproxy outbound --- .github/CRONET_GO_VERSION | 1 + .github/update_cronet.sh | 12 + .github/workflows/build.yml | 185 ++++++++- .github/workflows/docker.yml | 149 +++++++- .github/workflows/linux.yml | 70 +++- Dockerfile.binary | 8 + Makefile | 4 +- cmd/internal/build_libbox/main.go | 2 +- daemon/started_service.go | 4 +- docs/configuration/outbound/index.md | 1 + docs/configuration/outbound/index.zh.md | 1 + docs/configuration/outbound/naive.md | 68 ++++ docs/configuration/outbound/naive.zh.md | 68 ++++ docs/installation/build-from-source.md | 33 +- docs/installation/build-from-source.zh.md | 31 ++ go.mod | 28 +- go.sum | 56 ++- include/naive_outbound.go | 12 + include/naive_outbound_stub.go | 20 + include/registry.go | 1 + option/naive.go | 15 +- protocol/naive/inbound.go | 4 - protocol/naive/outbound.go | 179 +++++++++ test/box_test.go | 2 +- test/go.mod | 86 +++-- test/go.sum | 174 ++++++--- test/naive_self_test.go | 442 ++++++++++++++++++++++ 27 files changed, 1521 insertions(+), 135 deletions(-) create mode 100644 .github/CRONET_GO_VERSION create mode 100755 .github/update_cronet.sh create mode 100644 Dockerfile.binary create mode 100644 docs/configuration/outbound/naive.md create mode 100644 docs/configuration/outbound/naive.zh.md create mode 100644 include/naive_outbound.go create mode 100644 include/naive_outbound_stub.go create mode 100644 protocol/naive/outbound.go create mode 100644 test/naive_self_test.go diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION new file mode 100644 index 00000000..7edb0b99 --- /dev/null +++ b/.github/CRONET_GO_VERSION @@ -0,0 +1 @@ +3fb4098ed7e4ffe2e3d9593a744fc3717dbb369b diff --git a/.github/update_cronet.sh b/.github/update_cronet.sh new file mode 100755 index 00000000..4cddbfd5 --- /dev/null +++ b/.github/update_cronet.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +SCRIPT_DIR=$(dirname "$0") +PROJECTS=$SCRIPT_DIR/../.. + +git -C $PROJECTS/cronet-go fetch origin main +git -C $PROJECTS/cronet-go fetch origin go +go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go) +go mod tidy +git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$SCRIPT_DIR/CRONET_GO_VERSION" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d2aee6c..11f45e6a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,13 +69,23 @@ jobs: strategy: matrix: include: - - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - - { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" } + - { os: linux, arch: amd64, variant: purego, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } + - { os: linux, arch: amd64, variant: glibc, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } + - { os: linux, arch: amd64, variant: musl, naive: true } + + - { os: linux, arch: arm64, variant: purego, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } + - { os: linux, arch: arm64, variant: glibc, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } + - { os: linux, arch: arm64, variant: musl, naive: true } + + - { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" } + - { os: linux, arch: "386", variant: musl, naive: true, go386: sse2 } + + - { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } + - { os: linux, arch: arm, variant: musl, naive: true, goarm: "7" } + - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" } - - { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" } - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" } - - { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } - { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" } - { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" } - { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" } @@ -87,11 +97,8 @@ jobs: - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" } - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" } - - { os: windows, arch: amd64 } - { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" } - - { os: windows, arch: "386" } - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" } - - { os: windows, arch: arm64 } - { os: android, arch: arm64, ndk: "aarch64-linux-android21" } - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } @@ -135,6 +142,45 @@ jobs: with: ndk-version: r28 local-cache: true + - name: Clone cronet-go + if: matrix.naive + run: | + set -xeuo pipefail + CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) + git init ~/cronet-go + git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git + git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" + git -C ~/cronet-go checkout FETCH_HEAD + git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Cache Chromium toolchain + if: matrix.naive + id: cache-chromium-toolchain + uses: actions/cache@v4 + with: + path: | + ~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts + ~/cronet-go/naiveproxy/src/out/sysroot-build + key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }} + - name: Download Chromium toolchain + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + if [[ "${{ matrix.variant }}" == "musl" ]]; then + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain + else + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain + fi + - name: Set Chromium toolchain environment + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + if [[ "${{ matrix.variant }}" == "musl" ]]; then + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV + else + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV + fi - name: Set tag run: |- git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" @@ -143,9 +189,64 @@ jobs: run: | set -xeuo pipefail TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + if [[ "${{ matrix.naive }}" == "true" ]]; then + TAGS="${TAGS},with_naive_outbound" + fi + if [[ "${{ matrix.variant }}" == "purego" ]]; then + TAGS="${TAGS},with_purego" + elif [[ "${{ matrix.variant }}" == "musl" ]]; then + TAGS="${TAGS},with_musl" + fi echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - - name: Build - if: matrix.os != 'android' + - name: Build (purego) + if: matrix.variant == 'purego' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build (glibc) + if: matrix.variant == 'glibc' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build (musl) + if: matrix.variant == 'musl' + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GO386: ${{ matrix.go386 }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build (non-variant) + if: matrix.os != 'android' && matrix.variant == '' run: | set -xeuo pipefail mkdir -p dist @@ -189,6 +290,11 @@ jobs: elif [[ -n "${{ matrix.legacy_name }}" ]]; then DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}" fi + if [[ "${{ matrix.variant }}" == "glibc" ]]; then + DIR_NAME="${DIR_NAME}-glibc" + elif [[ "${{ matrix.variant }}" == "musl" ]]; then + DIR_NAME="${DIR_NAME}-musl" + fi echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" PKG_VERSION="${{ needs.calculate_version.outputs.version }}" PKG_VERSION="${PKG_VERSION//-/\~}" @@ -279,7 +385,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }} + name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}${{ matrix.variant && format('-{0}', matrix.variant) }} path: "dist" build_darwin: name: Build Darwin binaries @@ -316,6 +422,9 @@ jobs: run: | set -xeuo pipefail TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then + TAGS="${TAGS},with_naive_outbound" + fi echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - name: Build run: | @@ -352,6 +461,57 @@ jobs: with: name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }} path: "dist" + build_windows: + name: Build Windows binaries + if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' + runs-on: windows-latest + needs: + - calculate_version + strategy: + matrix: + include: + - { arch: amd64 } + - { arch: "386" } + - { arch: arm64 } + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.25.4 + - name: Set tag + run: |- + git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV" + git tag v${{ needs.calculate_version.outputs.version }} -f + - name: Build + run: | + mkdir -p dist + go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` + -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" ` + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: windows + GOARCH: ${{ matrix.arch }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Archive + run: | + $DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}" + mkdir "dist/$DIR_NAME" + Copy-Item LICENSE "dist/$DIR_NAME" + Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME" + Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip" + Remove-Item -Recurse "dist/$DIR_NAME" + - name: Cleanup + run: Remove-Item dist/sing-box.exe + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: binary-windows_${{ matrix.arch }} + path: "dist" build_android: name: Build Android if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' @@ -500,7 +660,7 @@ jobs: build_apple: name: Build Apple clients runs-on: macos-26 - if: false + if: false # github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone' needs: - calculate_version strategy: @@ -665,7 +825,7 @@ jobs: --app-drop-link 0 0 \ --skip-jenkins \ SFM.dmg "${{ matrix.export_path }}/SFM.app" - xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password" + xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password" cd "${{ matrix.archive }}" zip -r SFM.dSYMs.zip dSYMs popd @@ -687,6 +847,7 @@ jobs: - calculate_version - build - build_darwin + - build_windows - build_android - build_apple steps: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a4432a21..e4041b5b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,10 @@ name: Publish Docker Images on: + #push: + # branches: + # - main-next + # - dev-next release: types: - published @@ -13,8 +17,134 @@ env: REGISTRY_IMAGE: ghcr.io/sagernet/sing-box jobs: - build: + build_binary: + name: Build binary runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + include: + # Naive-enabled builds (musl) + - { arch: amd64, naive: true, docker_platform: "linux/amd64" } + - { arch: arm64, naive: true, docker_platform: "linux/arm64" } + - { arch: "386", naive: true, docker_platform: "linux/386" } + - { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" } + # Non-naive builds + - { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" } + - { arch: ppc64le, docker_platform: "linux/ppc64le" } + - { arch: riscv64, docker_platform: "linux/riscv64" } + - { arch: s390x, docker_platform: "linux/s390x" } + steps: + - name: Get commit to build + id: ref + run: |- + if [[ -z "${{ github.event.inputs.tag }}" ]]; then + ref="${{ github.ref_name }}" + else + ref="${{ github.event.inputs.tag }}" + fi + echo "ref=$ref" + echo "ref=$ref" >> $GITHUB_OUTPUT + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + ref: ${{ steps.ref.outputs.ref }} + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ^1.25.4 + - name: Clone cronet-go + if: matrix.naive + run: | + set -xeuo pipefail + CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) + git init ~/cronet-go + git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git + git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" + git -C ~/cronet-go checkout FETCH_HEAD + git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Cache Chromium toolchain + if: matrix.naive + id: cache-chromium-toolchain + uses: actions/cache@v4 + with: + path: | + ~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts + ~/cronet-go/naiveproxy/src/out/sysroot-build + key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }} + - name: Download Chromium toolchain + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain + - name: Set version + run: | + set -xeuo pipefail + VERSION=$(go run ./cmd/internal/read_tag) + echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" + - name: Set Chromium toolchain environment + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV + - name: Set build tags + run: | + set -xeuo pipefail + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + if [[ "${{ matrix.naive }}" == "true" ]]; then + TAGS="${TAGS},with_naive_outbound,with_musl" + fi + echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Build (naive) + if: matrix.naive + run: | + set -xeuo pipefail + go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + - name: Build (non-naive) + if: ${{ ! matrix.naive }} + run: | + set -xeuo pipefail + go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \ + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + - name: Prepare artifact + run: | + platform=${{ matrix.docker_platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + # Rename binary to include arch info for Dockerfile.binary + BINARY_NAME="sing-box-${{ matrix.arch }}" + if [[ -n "${{ matrix.goarm }}" ]]; then + BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}" + fi + mv sing-box "${BINARY_NAME}" + echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: binary-${{ env.PLATFORM_PAIR }} + path: ${{ env.BINARY_NAME }} + if-no-files-found: error + retention-days: 1 + build_docker: + name: Build Docker image + runs-on: ubuntu-latest + needs: + - build_binary strategy: fail-fast: true matrix: @@ -47,6 +177,16 @@ jobs: run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + - name: Download binary + uses: actions/download-artifact@v5 + with: + name: binary-${{ env.PLATFORM_PAIR }} + path: . + - name: Prepare binary + run: | + # Find and make the binary executable + chmod +x sing-box-* + ls -la sing-box-* - name: Setup QEMU uses: docker/setup-qemu-action@v3 - name: Setup Docker Buildx @@ -68,8 +208,7 @@ jobs: with: platforms: ${{ matrix.platform }} context: . - build-args: | - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 + file: Dockerfile.binary labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true - name: Export digest @@ -87,7 +226,7 @@ jobs: merge: runs-on: ubuntu-latest needs: - - build + - build_docker steps: - name: Get commit to build id: ref @@ -121,6 +260,7 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Create manifest list and push + if: github.event_name != 'push' working-directory: /tmp/digests run: | docker buildx imagetools create \ @@ -128,6 +268,7 @@ jobs: -t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \ $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - name: Inspect image + if: github.event_name != 'push' run: | docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }} docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f2b8a50b..f755f2df 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,6 +1,10 @@ name: Build Linux Packages on: + #push: + # branches: + # - main-next + # - dev-next workflow_dispatch: inputs: version: @@ -52,11 +56,13 @@ jobs: strategy: matrix: include: - - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 } - - { os: linux, arch: "386", debian: i386, rpm: i386 } + # Naive-enabled builds (musl) + - { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 } + - { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 } + - { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 } + - { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl } + # Non-naive builds (unsupported architectures) - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl } - - { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl } - - { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 } - { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el } - { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel } - { os: linux, arch: s390x, debian: s390x, rpm: s390x } @@ -72,12 +78,37 @@ jobs: uses: actions/setup-go@v5 with: go-version: ^1.25.6 - - name: Setup Android NDK - if: matrix.os == 'android' - uses: nttld/setup-ndk@v1 + - name: Clone cronet-go + if: matrix.naive + run: | + set -xeuo pipefail + CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) + git init ~/cronet-go + git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git + git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" + git -C ~/cronet-go checkout FETCH_HEAD + git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Cache Chromium toolchain + if: matrix.naive + id: cache-chromium-toolchain + uses: actions/cache@v4 with: - ndk-version: r28 - local-cache: true + path: | + ~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts + ~/cronet-go/naiveproxy/src/out/sysroot-build + key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }} + - name: Download Chromium toolchain + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain + - name: Set Chromium toolchain environment + if: matrix.naive + run: | + set -xeuo pipefail + cd ~/cronet-go + go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV - name: Set tag run: |- git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" @@ -86,8 +117,26 @@ jobs: run: | set -xeuo pipefail TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + if [[ "${{ matrix.naive }}" == "true" ]]; then + TAGS="${TAGS},with_naive_outbound,with_musl" + fi echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" - - name: Build + - name: Build (naive) + if: matrix.naive + run: | + set -xeuo pipefail + mkdir -p dist + go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + ./cmd/sing-box + env: + CGO_ENABLED: "1" + GOOS: linux + GOARCH: ${{ matrix.arch }} + GOARM: ${{ matrix.goarm }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build (non-naive) + if: ${{ ! matrix.naive }} run: | set -xeuo pipefail mkdir -p dist @@ -185,5 +234,6 @@ jobs: path: dist merge-multiple: true - name: Publish packages + if: github.event_name != 'push' run: |- ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/ diff --git a/Dockerfile.binary b/Dockerfile.binary new file mode 100644 index 00000000..ac46e53a --- /dev/null +++ b/Dockerfile.binary @@ -0,0 +1,8 @@ +FROM alpine +ARG TARGETARCH +ARG TARGETVARIANT +LABEL maintainer="nekohasekai " +RUN set -ex \ + && apk add --no-cache --upgrade bash tzdata ca-certificates nftables +COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box +ENTRYPOINT ["sing-box"] diff --git a/Makefile b/Makefile index 2298e66e..c5e2d8ff 100644 --- a/Makefile +++ b/Makefile @@ -249,8 +249,8 @@ lib: go run ./cmd/internal/build_libbox -target ios lib_install: - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.10 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.10 docs: venv/bin/mkdocs serve diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 045d3dc5..f76ea492 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -62,7 +62,7 @@ func init() { sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0") - sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0") + sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0") darwinTags = append(darwinTags, "with_dhcp") memcTags = append(memcTags, "with_tailscale") notMemcTags = append(notMemcTags, "with_low_memory") diff --git a/daemon/started_service.go b/daemon/started_service.go index acbbc503..65adac5d 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -277,8 +277,8 @@ func (s *StartedService) SubscribeLog(empty *emptypb.Empty, server grpc.ServerSt for element := s.logLines.Front(); element != nil; element = element.Next() { savedLines = append(savedLines, element.Value) } - s.logAccess.Unlock() subscription, done, err := s.logObserver.Subscribe() + s.logAccess.Unlock() if err != nil { return err } @@ -816,13 +816,13 @@ func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() { func (s *StartedService) WriteMessage(level log.Level, message string) { item := &log.Entry{Level: level, Message: message} - s.logSubscriber.Emit(item) s.logAccess.Lock() s.logLines.PushBack(item) if s.logLines.Len() > s.logMaxLines { s.logLines.Remove(s.logLines.Front()) } s.logAccess.Unlock() + s.logSubscriber.Emit(item) if s.debug { s.handler.WriteDebugMessage(message) } diff --git a/docs/configuration/outbound/index.md b/docs/configuration/outbound/index.md index b6094a15..47b8a96a 100644 --- a/docs/configuration/outbound/index.md +++ b/docs/configuration/outbound/index.md @@ -36,6 +36,7 @@ | `dns` | [DNS](./dns/) | | `selector` | [Selector](./selector/) | | `urltest` | [URLTest](./urltest/) | +| `naive` | [NaiveProxy](./naive/) | #### tag diff --git a/docs/configuration/outbound/index.zh.md b/docs/configuration/outbound/index.zh.md index 1b6066e7..a1c4a7ad 100644 --- a/docs/configuration/outbound/index.zh.md +++ b/docs/configuration/outbound/index.zh.md @@ -36,6 +36,7 @@ | `dns` | [DNS](./dns/) | | `selector` | [Selector](./selector/) | | `urltest` | [URLTest](./urltest/) | +| `naive` | [NaiveProxy](./naive/) | #### tag diff --git a/docs/configuration/outbound/naive.md b/docs/configuration/outbound/naive.md new file mode 100644 index 00000000..8082687d --- /dev/null +++ b/docs/configuration/outbound/naive.md @@ -0,0 +1,68 @@ +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: Initial release + +### Structure + +```json +{ + "type": "naive", + "tag": "naive-out", + + "server": "127.0.0.1", + "server_port": 443, + "username": "sekai", + "password": "password", + "insecure_concurrency": 0, + "extra_headers": {}, + "tls": {}, + + ... // Dial Fields +} +``` + +!!! warning "" + + NaiveProxy outbound is only available on Apple platforms, Android, Windows and some Linux architectures, see [Build from source](/installation/build-from-source/#with_naive_outbound). + +### Fields + +#### server + +==Required== + +The server address. + +#### server_port + +==Required== + +The server port. + +#### username + +Authentication username. + +#### password + +Authentication password. + +#### insecure_concurrency + +Number of concurrent tunnel connections. Multiple connections make the tunneling easier to detect through traffic analysis, which defeats the purpose of NaiveProxy's design to resist traffic analysis. + +#### extra_headers + +Extra headers to send in HTTP requests. + +#### tls + +==Required== + +TLS configuration, see [TLS](/configuration/shared/tls/#outbound). + +Only `server_name`, `certificate`, `certificate_path` and `certificate_public_key_sha256` are supported. + +### Dial Fields + +See [Dial Fields](/configuration/shared/dial/) for details. diff --git a/docs/configuration/outbound/naive.zh.md b/docs/configuration/outbound/naive.zh.md new file mode 100644 index 00000000..487f048e --- /dev/null +++ b/docs/configuration/outbound/naive.zh.md @@ -0,0 +1,68 @@ +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: 初始版本 + +### 结构 + +```json +{ + "type": "naive", + "tag": "naive-out", + + "server": "127.0.0.1", + "server_port": 443, + "username": "sekai", + "password": "password", + "insecure_concurrency": 0, + "extra_headers": {}, + "tls": {}, + + ... // 拨号字段 +} +``` + +!!! warning "" + + NaiveProxy 出站仅在 Apple 平台、Android、Windows 和部分架构的 Linux 上可用,参阅 [从源代码构建](/zh/installation/build-from-source/#with_naive_outbound)。 + +### 字段 + +#### server + +==必填== + +服务器地址。 + +#### server_port + +==必填== + +服务器端口。 + +#### username + +认证用户名。 + +#### password + +认证密码。 + +#### insecure_concurrency + +并发隧道连接数。多连接使隧道更容易被流量分析检测,违背 NaiveProxy 抵抗流量分析的设计目的。 + +#### extra_headers + +HTTP 请求中发送的额外头部。 + +#### tls + +==必填== + +TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 + +只有 `server_name`、`certificate`、`certificate_path` 和 `certificate_public_key_sha256` 是被支持的。 + +### 拨号字段 + +参阅 [拨号字段](/zh/configuration/shared/dial/)。 diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index 1f13e814..b998cf7b 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -57,6 +57,37 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | | `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | | `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | -| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) | +| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) | +| `with_naive_outbound` | :material-close:️ | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/). | It is not recommended to change the default build tag list unless you really know what you are adding. + +## :material-layers: with_naive_outbound + +NaiveProxy outbound requires special build configurations depending on your target platform. + +### Supported Platforms + +| Platform | Architectures | Mode | Requirements | +|-----------------|--------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------| +| Windows | * | purego | None | +| Linux | amd64, arm64 | purego | Download libcronet from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) to system library path or sing-box binary directory | +| Linux | 386, amd64, arm, arm64 | CGO | Chromium toolchain (see [cronet-go](https://github.com/sagernet/cronet-go)) | +| Apple platforms | * | CGO | Xcode | +| Android | * | CGO | Android NDK | + +### Windows + +Use `with_purego` tag. + +### Linux (purego, amd64/arm64 only) + +Download `libcronet.so` from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and install to system library path or the same directory as sing-box binary, then use `with_purego` tag. + +### Linux (CGO) + +See [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions). + +### Apple platforms / Android + +See [cronet-go](https://github.com/sagernet/cronet-go). diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index 512d2e24..ecb09fce 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -62,5 +62,36 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | | `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | | `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) | +| `with_naive_outbound` | :material-close:️ | 构建 NaiveProxy 出站支持,参阅 [NaiveProxy 出站](/zh/configuration/outbound/naive/)。 | 除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。 + +## :material-layers: with_naive_outbound + +NaiveProxy 出站需要根据目标平台进行特殊的构建配置。 + +### 支持的平台 + +| 平台 | 架构 | 模式 | 要求 | +|---------------|----------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------| +| Windows | * | purego | 无 | +| Linux | amd64, arm64 | purego | 从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载 libcronet 到系统库目录或 sing-box 二进制文件相同目录 | +| Linux | 386, amd64, arm, arm64 | CGO | Chromium 工具链(参阅 [cronet-go](https://github.com/sagernet/cronet-go)) | +| Apple 平台 | * | CGO | Xcode | +| Android | * | CGO | Android NDK | + +### Windows + +使用 `with_purego` 标记。 + +### Linux (purego, 仅 amd64/arm64) + +从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载 `libcronet.so` 并安装到系统库目录或 sing-box 二进制文件相同目录,然后使用 `with_purego` 标记。 + +### Linux (CGO) + +参阅 [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions)。 + +### Apple 平台 / Android + +参阅 [cronet-go](https://github.com/sagernet/cronet-go)。 diff --git a/go.mod b/go.mod index 7be6b685..83aa2ce2 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,10 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 + github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 + github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c github.com/sagernet/fswatch v0.1.1 - github.com/sagernet/gomobile v0.1.8 + github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 @@ -73,6 +75,7 @@ require ( github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gaissmai/bart v0.18.0 // indirect @@ -104,6 +107,29 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index 0d6d29d1..66253879 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbY github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -149,10 +151,60 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= +github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 h1:ql2eCQp1sIinoSwNcJW+tBGToRoxm0rsU8uqRJA9Vao= +github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= +github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c h1:3qNxvssYmfARhUtSFRbleSeSVoShwUEoxl42hqL75hA= +github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c/go.mod h1:/liG19g+SCq7pyaZtUHeGqUWckKfdMjR0DR83hNcxdw= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6 h1:/4EvUgxjKFPSnlitNV90te7p4/Ywpxz1/zJuB8BT5Qs= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:HgpJekgPVMKdODAmz6MwPBzz6dq4nImGy/5/jqD1N90= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6 h1:3YsRLuWT0vEjIgsSdv8g45RuOMUDloO1rEsW/990CPQ= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:1TwUqHR7/gV1hTxuhlu4qWlIN5IpPWkVA/0E8Bdjtgo= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:FdA9JPgmoPYHxNgqKMyNztNyOFsxdrRvGOCxMIlHnjg= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:69q8UN4r6wy15I7VGILeApQLYUMfeFzO+uZBA68vqpM= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:2QBSQdCMrsAQOugFvgsPJpDc+JQsS6JP/JBId1YxX/g= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:4SUxagz2PuS7I3jNf55K50ALHtVyvV8PF2/1wnfssMk= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:5tkFlEyQ9zF4AX7asWePmwF3hQSdrrf3a8EaQ7mJ0IE= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6 h1:lLnVN2IC00au+Vc4V2ijAWdbzlXg9r/bLxm50gKT7i4= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6 h1:SUhkusdTwrPZdrUTlyyF6NcnCdET2f1TFdE4+d6YVDI= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:aFFVgvDDBuNJKrU6Bw5btlLsX+JLyXatXmHK7KePT+c= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6 h1:VW5xkrH8Quve1UC5Py37KYHLrJN9vgLJc+raIUO6d/Q= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6 h1:YQIccJVMwMPo9yy7VB0Un/fnSMt7nDh9NS/kwfctpPA= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:dyRIgfMHkCKaSOPyjYFbXmVJDTEoSRQlk6mgBN7RuUA= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6 h1:N/4oDOsP5lCNoy8UGo4v+LdCRXOnG+uzLLNhywuaQVU= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6 h1:AsYRIu6HEZ5+1ONzzsGEk6kGJzIKPflhIXhviRQUuHg= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:63TxOukZ5bMPrQurR2S6RPCRFydKErBCJq4oLA+lPb4= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:WO4WmUXYh0TqmGLQ5SQyk4A05l+GSKcoFyBzW8I7kd0= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:WQU1wWPwKOENmaSD68ltFdnn/FrdLpjJzNMjkeLGmyA= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6 h1:1rweIHAKs+8yc1VZ/N4kod9RPJARgF5gDc4e1wh6rQo= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:NNcFjL2/F3Ux8mJuSAJKmMGcrLmkdas1X9DWuvY7BKU= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:TCgnN8XGroby1RkMKZgaQjCKKlVlmERtaNokKW4nyyo= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= -github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo= -github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= +github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= +github.com/sagernet/gomobile v0.1.10/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o= github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/include/naive_outbound.go b/include/naive_outbound.go new file mode 100644 index 00000000..d15d0450 --- /dev/null +++ b/include/naive_outbound.go @@ -0,0 +1,12 @@ +//go:build with_naive_outbound + +package include + +import ( + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/naive" +) + +func registerNaiveOutbound(registry *outbound.Registry) { + naive.RegisterOutbound(registry) +} diff --git a/include/naive_outbound_stub.go b/include/naive_outbound_stub.go new file mode 100644 index 00000000..cf892091 --- /dev/null +++ b/include/naive_outbound_stub.go @@ -0,0 +1,20 @@ +//go:build !with_naive_outbound + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerNaiveOutbound(registry *outbound.Registry) { + outbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) { + return nil, E.New(`naive outbound is not included in this build, rebuild with -tags with_naive_outbound`) + }) +} diff --git a/include/registry.go b/include/registry.go index 9965bc5e..8f08189d 100644 --- a/include/registry.go +++ b/include/registry.go @@ -86,6 +86,7 @@ func OutboundRegistry() *outbound.Registry { shadowsocks.RegisterOutbound(registry) vmess.RegisterOutbound(registry) trojan.RegisterOutbound(registry) + registerNaiveOutbound(registry) tor.RegisterOutbound(registry) ssh.RegisterOutbound(registry) shadowtls.RegisterOutbound(registry) diff --git a/option/naive.go b/option/naive.go index 0b19f264..50f03454 100644 --- a/option/naive.go +++ b/option/naive.go @@ -1,6 +1,9 @@ package option -import "github.com/sagernet/sing/common/auth" +import ( + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" +) type NaiveInboundOptions struct { ListenOptions @@ -8,3 +11,13 @@ type NaiveInboundOptions struct { Network NetworkList `json:"network,omitempty"` InboundTLSOptionsContainer } + +type NaiveOutboundOptions struct { + DialerOptions + ServerOptions + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + InsecureConcurrency int `json:"insecure_concurrency,omitempty"` + ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"` + OutboundTLSOptionsContainer +} diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 4ffca7e2..6354f011 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -91,10 +91,6 @@ func (n *Inbound) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } - tlsConfig, err = n.tlsConfig.STDConfig() - if err != nil { - return err - } } if common.Contains(n.network, N.NetworkTCP) { tcpListener, err := n.listener.ListenTCP() diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go new file mode 100644 index 00000000..48209ae8 --- /dev/null +++ b/protocol/naive/outbound.go @@ -0,0 +1,179 @@ +//go:build with_naive_outbound + +package naive + +import ( + "context" + "net" + "os" + "strings" + + "github.com/sagernet/cronet-go" + _ "github.com/sagernet/cronet-go/all" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/dialer" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + 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" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.NaiveOutboundOptions](registry, C.TypeNaive, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + ctx context.Context + logger logger.ContextLogger + client *cronet.NaiveClient +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, C.ErrTLSRequired + } + if options.TLS.DisableSNI { + return nil, E.New("disable_sni is not supported on naive outbound") + } + if options.TLS.Insecure { + return nil, E.New("insecure is not supported on naive outbound") + } + if len(options.TLS.ALPN) > 0 { + return nil, E.New("alpn is not supported on naive outbound") + } + if options.TLS.MinVersion != "" { + return nil, E.New("min_version is not supported on naive outbound") + } + if options.TLS.MaxVersion != "" { + return nil, E.New("max_version is not supported on naive outbound") + } + if len(options.TLS.CipherSuites) > 0 { + return nil, E.New("cipher_suites is not supported on naive outbound") + } + if len(options.TLS.CurvePreferences) > 0 { + return nil, E.New("curve_preferences is not supported on naive outbound") + } + if len(options.TLS.ClientCertificate) > 0 || options.TLS.ClientCertificatePath != "" { + return nil, E.New("client_certificate is not supported on naive outbound") + } + if len(options.TLS.ClientKey) > 0 || options.TLS.ClientKeyPath != "" { + return nil, E.New("client_key is not supported on naive outbound") + } + if options.TLS.Fragment || options.TLS.RecordFragment { + return nil, E.New("fragment is not supported on naive outbound") + } + if options.TLS.KernelTx || options.TLS.KernelRx { + return nil, E.New("kernel TLS is not supported on naive outbound") + } + if options.TLS.ECH != nil && options.TLS.ECH.Enabled { + return nil, E.New("ECH is not currently supported on naive outbound") + } + if options.TLS.UTLS != nil && options.TLS.UTLS.Enabled { + return nil, E.New("uTLS is not supported on naive outbound") + } + if options.TLS.Reality != nil && options.TLS.Reality.Enabled { + return nil, E.New("reality is not supported on naive outbound") + } + + serverAddress := options.ServerOptions.Build() + + var serverName string + if options.TLS.ServerName != "" { + serverName = options.TLS.ServerName + } else { + serverName = serverAddress.AddrString() + } + + outboundDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: options.DialerOptions, + RemoteIsDomain: true, + ResolverOnDetour: true, + NewDialer: true, + }) + if err != nil { + return nil, err + } + + var trustedRootCertificates string + if len(options.TLS.Certificate) > 0 { + trustedRootCertificates = strings.Join(options.TLS.Certificate, "\n") + } else if options.TLS.CertificatePath != "" { + content, err := os.ReadFile(options.TLS.CertificatePath) + if err != nil { + return nil, E.Cause(err, "read certificate") + } + trustedRootCertificates = string(content) + } + + extraHeaders := make(map[string]string) + for key, values := range options.ExtraHeaders.Build() { + if len(values) > 0 { + extraHeaders[key] = values[0] + } + } + + client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{ + Context: ctx, + ServerAddress: serverAddress, + ServerName: serverName, + Username: options.Username, + Password: options.Password, + Concurrency: options.InsecureConcurrency, + ExtraHeaders: extraHeaders, + TrustedRootCertificates: trustedRootCertificates, + CertificatePublicKeySHA256: options.TLS.CertificatePublicKeySHA256, + Dialer: outboundDialer, + }) + if err != nil { + return nil, err + } + + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, []string{N.NetworkTCP}, options.DialerOptions), + ctx: ctx, + logger: logger, + client: client, + }, nil +} + +func (o *Outbound) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + err := o.client.Start() + if err != nil { + return err + } + o.logger.Info("NaiveProxy started, version: ", o.client.Engine().Version()) + return nil +} + +func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + ctx, metadata := adapter.ExtendContext(ctx) + metadata.Outbound = o.Tag() + metadata.Destination = destination + o.logger.InfoContext(ctx, "outbound connection to ", destination) + return o.client.DialContext(ctx, destination) +} + +func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +func (o *Outbound) Close() error { + return o.client.Close() +} + +func (o *Outbound) StartNetLogToFile(fileName string, logAll bool) bool { + return o.client.Engine().StartNetLogToFile(fileName, logAll) +} + +func (o *Outbound) StopNetLog() { + o.client.Engine().StopNetLog() +} diff --git a/test/box_test.go b/test/box_test.go index 152948d2..d7d9b9b0 100644 --- a/test/box_test.go +++ b/test/box_test.go @@ -88,7 +88,7 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) { func testQUIC(t *testing.T, clientPort uint16) { dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "") client := &http.Client{ - Transport: &http3.RoundTripper{ + Transport: &http3.Transport{ Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { destination := M.ParseSocksaddr(addr) udpConn, err := dialer.DialContext(ctx, N.NetworkUDP, destination) diff --git a/test/go.mod b/test/go.mod index cca30099..1695a452 100644 --- a/test/go.mod +++ b/test/go.mod @@ -1,8 +1,6 @@ module test -go 1.23.1 - -toolchain go1.24.0 +go 1.24.7 require github.com/sagernet/sing-box v0.0.0 @@ -12,15 +10,15 @@ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/gofrs/uuid/v5 v5.3.2 - github.com/sagernet/quic-go v0.52.0-beta.1 - github.com/sagernet/sing v0.7.8-0.20250909124511-ab3827767cea - github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5 + github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 + github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 + github.com/sagernet/sing-quic v0.6.0-beta.5 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/spyzhov/ajson v0.9.4 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.43.0 + golang.org/x/net v0.44.0 ) require ( @@ -30,26 +28,29 @@ require ( github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/anytls/sing-anytls v0.0.8 // indirect - github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/anthropics/anthropic-sdk-go v1.14.0 // indirect + github.com/anytls/sing-anytls v0.0.11 // indirect github.com/caddyserver/certmagic v0.23.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/coder/websocket v1.8.13 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/cretz/bine v0.2.0 // indirect + github.com/database64128/netx-go v0.1.1 // indirect + github.com/database64128/tfo-go/v2 v2.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gaissmai/bart v0.11.1 // indirect + github.com/gaissmai/bart v0.18.0 // indirect github.com/go-chi/chi/v5 v5.2.2 // indirect github.com/go-chi/render v1.0.3 // indirect - github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect + github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -62,16 +63,14 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/illarion/gonotify/v2 v2.0.3 // indirect + github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect + github.com/keybase/go-keychain v0.0.1 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect github.com/libdns/libdns v1.1.0 // indirect @@ -80,8 +79,7 @@ require ( github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect - github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 // indirect - github.com/metacubex/utls v1.8.0 // indirect + github.com/metacubex/utls v1.8.3 // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect github.com/miekg/dns v1.1.67 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect @@ -94,30 +92,54 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect - github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cors v1.2.1 // indirect + github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 // indirect + github.com/sagernet/cronet-go/all v0.0.0-20251212022647-84c3c9e2a88e // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/sing-mux v0.3.3 // indirect github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect - github.com/sagernet/sing-tun v0.8.0-beta.1.0.20250909100419-a8cb01e6df93 // indirect + github.com/sagernet/sing-tun v0.8.0-beta.11 // indirect github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect github.com/sagernet/smux v1.5.34-mod.2 // indirect - github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 // indirect - github.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect + github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 // indirect + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect - github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect + github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -133,15 +155,15 @@ require ( go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.37.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect diff --git a/test/go.sum b/test/go.sum index 06bc7792..7d3ee098 100644 --- a/test/go.sum +++ b/test/go.sum @@ -12,10 +12,10 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6Vymc= -github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/anthropics/anthropic-sdk-go v1.14.0 h1:EzNQvnZlaDHe2UPkoUySDz3ixRgNbwKdH8KtFpv7pi4= +github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= +github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= @@ -32,6 +32,10 @@ github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6 github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= +github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM= +github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc= +github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw= +github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -48,22 +52,24 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc= -github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg= +github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= +github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84= -github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= +github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -89,36 +95,30 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M= -github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A= -github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE= +github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= +github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU= github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -140,10 +140,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc= -github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= -github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac= -github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ= +github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= +github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= @@ -171,8 +169,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= @@ -181,6 +179,46 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= +github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 h1:ql2eCQp1sIinoSwNcJW+tBGToRoxm0rsU8uqRJA9Vao= +github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= +github.com/sagernet/cronet-go/all v0.0.0-20251212022647-84c3c9e2a88e h1:8EbRGPgjPVd54A42d8kPHkBemndix3E9X4ynKvgrKPI= +github.com/sagernet/cronet-go/all v0.0.0-20251212022647-84c3c9e2a88e/go.mod h1:LXgFATxmlI7hhzwYYsiu1IhhRFCMykcE/xhehiIeUMo= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251212022311-629f90088dc7 h1:eRDi6flT6kKRXKQanFJqyEBgvGlGjJ3cM1rC0ClaL9o= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251212022311-629f90088dc7/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251212022311-629f90088dc7 h1:t8YuHZccd0VLN4sdzBsum3k1UHlgPfsM3VZ1QWTXz6U= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251212022311-629f90088dc7 h1:Eqg+x5hVwau/GeVoS6OfL9gtrqnNgYgA0rQx3r60UGY= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251212022311-629f90088dc7/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251212022311-629f90088dc7 h1:dXDL2R+cI8uGH2AmWzIAqvSrnybMXgZCtSH7woMdqug= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251212022311-629f90088dc7 h1:Cnm7ZmvIU0lKCw9j1cOj95wOottI20UeFUKhCRwWoYM= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251212022311-629f90088dc7 h1:Oq4kAHfgSSR0y1+4WtrhZ9uHPBOns3JKlAaIpTp1JEc= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251212022311-629f90088dc7 h1:m4pXckZD0tAwdVzDW2VDEpa0VmU1xK9zkoDpGmW/DeA= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251212022311-629f90088dc7 h1:GHPHZwYvdA+nuyBy3BbFqdoZBNIaerbpu9cpSWddYAw= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251212022311-629f90088dc7/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251212022311-629f90088dc7 h1:r4dbGMdvZTPbaqU0yHHgMuB589tAcK8smJNFkZp7HLI= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251212022311-629f90088dc7 h1:Wn6FoJyJMfQhz8HAKjdCiDNzcwLIsWaqOZnhodY/Me8= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251212022311-629f90088dc7 h1:Dl/IYvylbtHw3AZx9RQyqCZGTS12e/NGDSZuKXlXWHw= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251212022311-629f90088dc7 h1:/vwk591fhV7laT03+uxaqnvueEpF9m1dyNO7r/aGFGA= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251212022311-629f90088dc7/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251212022311-629f90088dc7 h1:deoqtkWRhtDdnYlMEKvbCByzTVlK6FF/oVx94Nee5gc= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251212022311-629f90088dc7 h1:jc4TolAOHA2GRtUaGF2BiJyW1bwYjWh4ysHo+fTarwc= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251212022311-629f90088dc7 h1:+GThq5QNch7aZDOylMnTzCoxYLVC33e3hTP/yqp3BV4= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251212022311-629f90088dc7 h1:TrjtzYFKw3ibgO5KFM4Q6K24IXx1U4+fOlV1sklnZ9I= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251212022311-629f90088dc7/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251212022311-629f90088dc7 h1:zwGAXQo4rvonaUAwd+TUlpeYMcsCW1lE6JomxAxLTmc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251212022311-629f90088dc7 h1:ohNspkx6sP6iKJyFvuecreQFQPpbC61kFoZTquSJ7q4= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= @@ -189,31 +227,31 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= -github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.7.8-0.20250909124511-ab3827767cea h1:vkWFzPVlqnKq3FMpmh43ZVDbqHWapbv0Sh3vQc8oo7o= -github.com/sagernet/sing v0.7.8-0.20250909124511-ab3827767cea/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= +github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5 h1:vnRNLE0bBnz5NNbBoFH7NA7mlvNSa2Z4w+1Eb8pyX48= -github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5/go.mod h1:gi/sGED8gTWgTAp3GlzXo2D7mXYY+ERoxtGvSkNx3sI= +github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4= +github.com/sagernet/sing-quic v0.6.0-beta.5/go.mod h1:9D9GANrK33NjWCe1VkU5L5+8MxU39WrduBSmHuHz8GA= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.1.0.20250909100419-a8cb01e6df93 h1:jGkwe0Uk5litEUnvHO/c0nukm2FqvdwKHJio4kJIOxM= -github.com/sagernet/sing-tun v0.8.0-beta.1.0.20250909100419-a8cb01e6df93/go.mod h1:LokZYuEV3crByjQc/XRohLgfNvybtXdx5qe/I4W6S7k= +github.com/sagernet/sing-tun v0.8.0-beta.11 h1:xVi8VcVkvz2o+3v1PLv5MOkFpiVCwjLjucVlmigDi5c= +github.com/sagernet/sing-tun v0.8.0-beta.11/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= -github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 h1:cWM1iPwqIE1t06ft80wpvFB4xbhOpIFI+TFnTw2gnbs= -github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= -github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= -github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= +github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos= +github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -229,14 +267,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw= -github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= @@ -247,8 +283,20 @@ github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+y github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= +github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= @@ -300,30 +348,30 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= -golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= -golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 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= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -335,24 +383,24 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= 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= @@ -377,6 +425,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= +gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= diff --git a/test/naive_self_test.go b/test/naive_self_test.go new file mode 100644 index 00000000..6f9aa47f --- /dev/null +++ b/test/naive_self_test.go @@ -0,0 +1,442 @@ +package main + +import ( + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "net/netip" + "os" + "strings" + "testing" + + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/json/badoption" + "github.com/sagernet/sing/common/network" + + "github.com/stretchr/testify/require" +) + +func TestNaiveSelf(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkTCP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + testTCP(t, clientPort, testPort) +} + +func TestNaiveSelfPublicKeySHA256(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + + // Read and parse the server certificate to get its public key SHA256 + certPemContent, err := os.ReadFile(certPem) + require.NoError(t, err) + block, _ := pem.Decode(certPemContent) + require.NotNil(t, block) + cert, err := x509.ParseCertificate(block.Bytes) + require.NoError(t, err) + + // Calculate SHA256 of SPKI (Subject Public Key Info) + spkiBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + require.NoError(t, err) + pinHash := sha256.Sum256(spkiBytes) + + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkTCP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePublicKeySHA256: [][]byte{pinHash[:]}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + testTCP(t, clientPort, testPort) +} + +func TestNaiveSelfECH(t *testing.T) { + t.Skip("TODO: ECH is not currently supported on naive outbound") + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org")) + instance := startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkTCP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + ECH: &option.InboundECHOptions{ + Enabled: true, + Key: []string{echKey}, + }, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + ECH: &option.OutboundECHOptions{ + Enabled: true, + Config: []string{echConfig}, + }, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + + naiveOut, ok := instance.Outbound().Outbound("naive-out") + require.True(t, ok) + naiveOutbound := naiveOut.(*naive.Outbound) + + netLogPath := "/tmp/naive_ech_netlog.json" + require.True(t, naiveOutbound.StartNetLogToFile(netLogPath, true)) + defer naiveOutbound.StopNetLog() + + testTCP(t, clientPort, testPort) + + naiveOutbound.StopNetLog() + + logContent, err := os.ReadFile(netLogPath) + require.NoError(t, err) + logStr := string(logContent) + + require.True(t, strings.Contains(logStr, `"encrypted_client_hello":true`), + "ECH should be accepted in TLS handshake. NetLog saved to: %s", netLogPath) +} + +func TestNaiveSelfInsecureConcurrency(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + + instance := startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkTCP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + InsecureConcurrency: 3, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + + naiveOut, ok := instance.Outbound().Outbound("naive-out") + require.True(t, ok) + naiveOutbound := naiveOut.(*naive.Outbound) + + netLogPath := "/tmp/naive_concurrency_netlog.json" + require.True(t, naiveOutbound.StartNetLogToFile(netLogPath, true)) + defer naiveOutbound.StopNetLog() + + // Send multiple sequential connections to trigger round-robin + // With insecure_concurrency=3, connections will be distributed to 3 pools + for i := 0; i < 6; i++ { + testTCP(t, clientPort, testPort) + } + + naiveOutbound.StopNetLog() + + // Verify NetLog contains multiple independent HTTP/2 sessions + logContent, err := os.ReadFile(netLogPath) + require.NoError(t, err) + logStr := string(logContent) + + // Count HTTP2_SESSION_INITIALIZED events to verify connection pool isolation + // NetLog stores event types as numeric IDs, HTTP2_SESSION_INITIALIZED = 249 + sessionCount := strings.Count(logStr, `"type":249`) + require.GreaterOrEqual(t, sessionCount, 3, + "Expected at least 3 HTTP/2 sessions with insecure_concurrency=3. NetLog: %s", netLogPath) +} From 9b0960bb5a9e4ce9ab7e042e042e170baf207449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 15 Dec 2025 12:58:40 +0800 Subject: [PATCH 050/185] Fix bugs and add UoT option for naiveproxy outbound --- .github/CRONET_GO_VERSION | 2 +- .github/update_cronet.sh | 1 + .github/workflows/build.yml | 28 ++++--- docs/configuration/outbound/naive.md | 13 ++- docs/configuration/outbound/naive.zh.md | 13 ++- go.mod | 50 ++++++------ go.sum | 100 ++++++++++++------------ mkdocs.yml | 1 + option/naive.go | 1 + protocol/naive/outbound.go | 95 ++++++++++++++-------- test/naive_self_test.go | 12 +-- 11 files changed, 185 insertions(+), 131 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 7edb0b99..7616cee1 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -3fb4098ed7e4ffe2e3d9593a744fc3717dbb369b +fe7ab107d3a222ca878b9a727d76075938ee7cde diff --git a/.github/update_cronet.sh b/.github/update_cronet.sh index 4cddbfd5..0acee45e 100755 --- a/.github/update_cronet.sh +++ b/.github/update_cronet.sh @@ -8,5 +8,6 @@ PROJECTS=$SCRIPT_DIR/../.. git -C $PROJECTS/cronet-go fetch origin main git -C $PROJECTS/cronet-go fetch origin go go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go) +go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go) go mod tidy git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$SCRIPT_DIR/CRONET_GO_VERSION" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 11f45e6a..27a03e0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,19 +69,21 @@ jobs: strategy: matrix: include: - - { os: linux, arch: amd64, variant: purego, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - - { os: linux, arch: amd64, variant: glibc, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - - { os: linux, arch: amd64, variant: musl, naive: true } + - { os: linux, arch: amd64, variant: purego, openwrt: "x86_64" } + - { os: linux, arch: amd64, variant: glibc, naive: true } + - { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - - { os: linux, arch: arm64, variant: purego, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - - { os: linux, arch: arm64, variant: glibc, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - - { os: linux, arch: arm64, variant: musl, naive: true } + - { os: linux, arch: arm64, variant: purego, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } + - { os: linux, arch: arm64, variant: glibc, naive: true } + - { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - - { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" } - - { os: linux, arch: "386", variant: musl, naive: true, go386: sse2 } + - { os: linux, arch: "386", go386: sse2, openwrt: "i386_pentium4" } + - { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 } + - { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" } - - { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } - - { os: linux, arch: arm, variant: musl, naive: true, goarm: "7" } + - { os: linux, arch: arm, goarm: "7", openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } + - { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" } + - { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" } - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" } @@ -362,8 +364,12 @@ jobs: -p "dist/openwrt.deb" \ --architecture all \ dist/sing-box=/usr/bin/sing-box + SUFFIX="" + if [[ "${{ matrix.variant }}" == "musl" ]]; then + SUFFIX="_musl" + fi for architecture in ${{ matrix.openwrt }}; do - .github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk" + .github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}${SUFFIX}.ipk" done rm "dist/openwrt.deb" - name: Archive diff --git a/docs/configuration/outbound/naive.md b/docs/configuration/outbound/naive.md index 8082687d..ffcb3a4f 100644 --- a/docs/configuration/outbound/naive.md +++ b/docs/configuration/outbound/naive.md @@ -1,6 +1,8 @@ -!!! quote "Changes in sing-box 1.13.0" +--- +icon: material/new-box +--- - :material-plus: Initial release +!!! question "Since sing-box 1.13.0" ### Structure @@ -15,6 +17,7 @@ "password": "password", "insecure_concurrency": 0, "extra_headers": {}, + "udp_over_tcp": false | {}, "tls": {}, ... // Dial Fields @@ -55,6 +58,12 @@ Number of concurrent tunnel connections. Multiple connections make the tunneling Extra headers to send in HTTP requests. +#### udp_over_tcp + +UDP over TCP protocol settings. + +See [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details. + #### tls ==Required== diff --git a/docs/configuration/outbound/naive.zh.md b/docs/configuration/outbound/naive.zh.md index 487f048e..0ed265be 100644 --- a/docs/configuration/outbound/naive.zh.md +++ b/docs/configuration/outbound/naive.zh.md @@ -1,6 +1,8 @@ -!!! quote "sing-box 1.13.0 中的更改" +--- +icon: material/new-box +--- - :material-plus: 初始版本 +!!! question "自 sing-box 1.13.0 起" ### 结构 @@ -15,6 +17,7 @@ "password": "password", "insecure_concurrency": 0, "extra_headers": {}, + "udp_over_tcp": false | {}, "tls": {}, ... // 拨号字段 @@ -55,6 +58,12 @@ HTTP 请求中发送的额外头部。 +#### udp_over_tcp + +UDP over TCP 配置。 + +参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp/)。 + #### tls ==必填== diff --git a/go.mod b/go.mod index 83aa2ce2..a0b13a0a 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 - github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c + github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7 + github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -107,29 +107,29 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index 66253879..84efb47e 100644 --- a/go.sum +++ b/go.sum @@ -151,56 +151,56 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 h1:ql2eCQp1sIinoSwNcJW+tBGToRoxm0rsU8uqRJA9Vao= -github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= -github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c h1:3qNxvssYmfARhUtSFRbleSeSVoShwUEoxl42hqL75hA= -github.com/sagernet/cronet-go/all v0.0.0-20251213155601-2094cc48331c/go.mod h1:/liG19g+SCq7pyaZtUHeGqUWckKfdMjR0DR83hNcxdw= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6 h1:/4EvUgxjKFPSnlitNV90te7p4/Ywpxz1/zJuB8BT5Qs= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:HgpJekgPVMKdODAmz6MwPBzz6dq4nImGy/5/jqD1N90= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6 h1:3YsRLuWT0vEjIgsSdv8g45RuOMUDloO1rEsW/990CPQ= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251213155152-e4fff13128e6/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:1TwUqHR7/gV1hTxuhlu4qWlIN5IpPWkVA/0E8Bdjtgo= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:FdA9JPgmoPYHxNgqKMyNztNyOFsxdrRvGOCxMIlHnjg= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:69q8UN4r6wy15I7VGILeApQLYUMfeFzO+uZBA68vqpM= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:2QBSQdCMrsAQOugFvgsPJpDc+JQsS6JP/JBId1YxX/g= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:4SUxagz2PuS7I3jNf55K50ALHtVyvV8PF2/1wnfssMk= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:5tkFlEyQ9zF4AX7asWePmwF3hQSdrrf3a8EaQ7mJ0IE= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6 h1:lLnVN2IC00au+Vc4V2ijAWdbzlXg9r/bLxm50gKT7i4= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6 h1:SUhkusdTwrPZdrUTlyyF6NcnCdET2f1TFdE4+d6YVDI= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:aFFVgvDDBuNJKrU6Bw5btlLsX+JLyXatXmHK7KePT+c= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6 h1:VW5xkrH8Quve1UC5Py37KYHLrJN9vgLJc+raIUO6d/Q= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6 h1:YQIccJVMwMPo9yy7VB0Un/fnSMt7nDh9NS/kwfctpPA= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251213155152-e4fff13128e6/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:dyRIgfMHkCKaSOPyjYFbXmVJDTEoSRQlk6mgBN7RuUA= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6 h1:N/4oDOsP5lCNoy8UGo4v+LdCRXOnG+uzLLNhywuaQVU= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6 h1:AsYRIu6HEZ5+1ONzzsGEk6kGJzIKPflhIXhviRQUuHg= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251213155152-e4fff13128e6/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:63TxOukZ5bMPrQurR2S6RPCRFydKErBCJq4oLA+lPb4= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:WO4WmUXYh0TqmGLQ5SQyk4A05l+GSKcoFyBzW8I7kd0= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6 h1:WQU1wWPwKOENmaSD68ltFdnn/FrdLpjJzNMjkeLGmyA= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251213155152-e4fff13128e6/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6 h1:1rweIHAKs+8yc1VZ/N4kod9RPJARgF5gDc4e1wh6rQo= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6 h1:NNcFjL2/F3Ux8mJuSAJKmMGcrLmkdas1X9DWuvY7BKU= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6 h1:TCgnN8XGroby1RkMKZgaQjCKKlVlmERtaNokKW4nyyo= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251213155152-e4fff13128e6/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7 h1:jb/nr5YECJ56gcAphQ7tBWierrBbaLT7v1MI9n3e/Gw= +github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= +github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7 h1:6PjoWjKnYrz/HmEezV1Z5K39EC8l+sek1V14aXlslyc= +github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:SrXj1iQMVqZcy8XINBJOhlBncfCe7DimX6mTRY+rdDw= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b h1:+Dk1yBvaKl49l8j3YFoEvraAdt7VMy7n2Qzrs40/ekI= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b h1:tjkKLyRhD1ePdl48SjW38o7yjW1fCJ2x2nyvq5e/8oE= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b h1:P++HSm1JhmkKbDskFNfQuR8aCTg5uEWe2/5qFfj+6YU= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b h1:Rbo1r5Mk8yWlZTC8gcyuQFv2BXUI1/wWMC9Vc+cJNQ8= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b h1:VA1M5Yw09HiBD+Zemq6mOBVwBd4pr47LMN9WKOVf62Q= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AdWIsXfKxH3/hGjiYqcUSc0fb+R4vONjfRaO0emwdNA= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:sg8SupaVsj0Krc4DKSC1n2quig08bRtmsF0/iwwXeAI= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b h1:0dmsm/vEAYxQjtH4sS/A8X6bf6YqS0I0Vc6oDZdnlRc= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:jnT/bYjzvdfGVgPEgZX0Mi0qkm8qcU/DluV+TqShVPg= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b h1:/NqFcrdXS3e3Ad+ILfrwXFw3urwwFsQ1XxrDW9PkU4E= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b h1:vqeLRyeHq++RCcuUriJflTQne7hldEVJ19Or0xwCIrs= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b h1:Xr7dFoKy0o2YdPl2JcU7GtM4NxQyS8vGovd6Aw4pX8I= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b h1:GEt+x1qXt8xicDSD4GXOHs0WrVec5HAo+HmBAXzkidg= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b h1:MbjH6TmLoXlAkBWoUzuNF2w0FPfOMY6Rj9T226fe858= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AP85VNYiACL8QQeXqCUB8hz5hFOUtgwReLELRhve/4c= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b h1:4uNGGiOrJsa2S+PteucoO/Qyzz7FWHNJw2ezOkS7QiM= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b h1:N5yoxOlynwvTgaJnEOsL3iuI6FFmDJy1toyNSU+vlLA= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:JKyBNyt/DWAutvuDFjFTi0dMe0bh5zG7UUpZHH8Uqzo= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b h1:m0sCMM6ry0+eXBuTPLGY9JYOVcIvtHcDEcstMo+oSTU= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:UURnlFD48/9wn7cdi1NqYQuTvJZEFuQShxX8pvk2Fsg= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b h1:jhwpI5IXK5RPvbk9+xUV9GAw2QeRZvcZprp4bJOP9e0= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b h1:qoleSwhzgH6jDSwqktbJCPDex4yMWtijcouGR8+BL+s= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b h1:v7eakED1u8ZTKjmqxa+Eu0S5ewK+r+mfEf9KI6ymu+I= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= diff --git a/mkdocs.yml b/mkdocs.yml index 951d9504..c49bfa2a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -158,6 +158,7 @@ nav: - Shadowsocks: configuration/outbound/shadowsocks.md - VMess: configuration/outbound/vmess.md - Trojan: configuration/outbound/trojan.md + - Naive: configuration/outbound/naive.md - WireGuard: configuration/outbound/wireguard.md - Hysteria: configuration/outbound/hysteria.md - ShadowTLS: configuration/outbound/shadowtls.md diff --git a/option/naive.go b/option/naive.go index 50f03454..d72390be 100644 --- a/option/naive.go +++ b/option/naive.go @@ -19,5 +19,6 @@ type NaiveOutboundOptions struct { Password string `json:"password,omitempty"` InsecureConcurrency int `json:"insecure_concurrency,omitempty"` ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"` + UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` OutboundTLSOptionsContainer } diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index 48209ae8..963709c0 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -16,10 +16,12 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "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" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/uot" ) func RegisterOutbound(registry *outbound.Registry) { @@ -28,9 +30,10 @@ func RegisterOutbound(registry *outbound.Registry) { type Outbound struct { outbound.Adapter - ctx context.Context - logger logger.ContextLogger - client *cronet.NaiveClient + ctx context.Context + logger logger.ContextLogger + client *cronet.NaiveClient + uotClient *uot.Client } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveOutboundOptions) (adapter.Outbound, error) { @@ -119,61 +122,85 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{ - Context: ctx, - ServerAddress: serverAddress, - ServerName: serverName, - Username: options.Username, - Password: options.Password, - Concurrency: options.InsecureConcurrency, - ExtraHeaders: extraHeaders, - TrustedRootCertificates: trustedRootCertificates, - CertificatePublicKeySHA256: options.TLS.CertificatePublicKeySHA256, - Dialer: outboundDialer, + Context: ctx, + ServerAddress: serverAddress, + ServerName: serverName, + Username: options.Username, + Password: options.Password, + InsecureConcurrency: options.InsecureConcurrency, + ExtraHeaders: extraHeaders, + TrustedRootCertificates: trustedRootCertificates, + TrustedCertificatePublicKeySHA256: options.TLS.CertificatePublicKeySHA256, + Dialer: outboundDialer, }) if err != nil { return nil, err } + var uotClient *uot.Client + uotOptions := common.PtrValueOrDefault(options.UDPOverTCP) + if uotOptions.Enabled { + uotClient = &uot.Client{ + Dialer: &naiveDialer{client}, + Version: uotOptions.Version, + } + } return &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, []string{N.NetworkTCP}, options.DialerOptions), - ctx: ctx, - logger: logger, - client: client, + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, []string{N.NetworkTCP}, options.DialerOptions), + ctx: ctx, + logger: logger, + client: client, + uotClient: uotClient, }, nil } -func (o *Outbound) Start(stage adapter.StartStage) error { +func (h *Outbound) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } - err := o.client.Start() + err := h.client.Start() if err != nil { return err } - o.logger.Info("NaiveProxy started, version: ", o.client.Engine().Version()) + h.logger.Info("NaiveProxy started, version: ", h.client.Engine().Version()) return nil } -func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = o.Tag() - metadata.Destination = destination - o.logger.InfoContext(ctx, "outbound connection to ", destination) - return o.client.DialContext(ctx, destination) +func (h *Outbound) 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.DialEarly(destination) + case N.NetworkUDP: + if h.uotClient == nil { + return nil, E.New("UDP is not supported unless UDP over TCP is enabled") + } + h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) + return h.uotClient.DialContext(ctx, network, destination) + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } } -func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + if h.uotClient == nil { + return nil, E.New("UDP is not supported unless UDP over TCP is enabled") + } + return h.uotClient.ListenPacket(ctx, destination) } -func (o *Outbound) Close() error { - return o.client.Close() +func (h *Outbound) Close() error { + return h.client.Close() } -func (o *Outbound) StartNetLogToFile(fileName string, logAll bool) bool { - return o.client.Engine().StartNetLogToFile(fileName, logAll) +func (h *Outbound) Client() *cronet.NaiveClient { + return h.client } -func (o *Outbound) StopNetLog() { - o.client.Engine().StopNetLog() +type naiveDialer struct { + *cronet.NaiveClient +} + +func (d *naiveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return d.NaiveClient.DialEarly(destination) } diff --git a/test/naive_self_test.go b/test/naive_self_test.go index 6f9aa47f..0352d373 100644 --- a/test/naive_self_test.go +++ b/test/naive_self_test.go @@ -310,12 +310,12 @@ func TestNaiveSelfECH(t *testing.T) { naiveOutbound := naiveOut.(*naive.Outbound) netLogPath := "/tmp/naive_ech_netlog.json" - require.True(t, naiveOutbound.StartNetLogToFile(netLogPath, true)) - defer naiveOutbound.StopNetLog() + require.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true)) + defer naiveOutbound.Client().Engine().StopNetLog() testTCP(t, clientPort, testPort) - naiveOutbound.StopNetLog() + naiveOutbound.Client().Engine().StopNetLog() logContent, err := os.ReadFile(netLogPath) require.NoError(t, err) @@ -418,8 +418,8 @@ func TestNaiveSelfInsecureConcurrency(t *testing.T) { naiveOutbound := naiveOut.(*naive.Outbound) netLogPath := "/tmp/naive_concurrency_netlog.json" - require.True(t, naiveOutbound.StartNetLogToFile(netLogPath, true)) - defer naiveOutbound.StopNetLog() + require.True(t, naiveOutbound.Client().Engine().StartNetLogToFile(netLogPath, true)) + defer naiveOutbound.Client().Engine().StopNetLog() // Send multiple sequential connections to trigger round-robin // With insecure_concurrency=3, connections will be distributed to 3 pools @@ -427,7 +427,7 @@ func TestNaiveSelfInsecureConcurrency(t *testing.T) { testTCP(t, clientPort, testPort) } - naiveOutbound.StopNetLog() + naiveOutbound.Client().Engine().StopNetLog() // Verify NetLog contains multiple independent HTTP/2 sessions logContent, err := os.ReadFile(netLogPath) From b919039c43dc46b72bdc4c4ede588d8684c9f8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 15 Dec 2025 14:59:38 +0800 Subject: [PATCH 051/185] release: Upload only other apks --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27a03e0d..635642b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -591,8 +591,8 @@ jobs: - name: Prepare upload run: |- mkdir -p dist - cp clients/android/app/build/outputs/apk/play/release/*.apk dist - cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist + #cp clients/android/app/build/outputs/apk/play/release/*.apk dist + cp clients/android/app/build/outputs/apk/other/release/*.apk dist - name: Upload artifact uses: actions/upload-artifact@v4 with: From a89680fa2df8c24ae411bd582bd01d6cac8c5db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 16 Dec 2025 22:08:50 +0800 Subject: [PATCH 052/185] Update pricing for CCM service --- service/ccm/service_usage.go | 71 +++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/service/ccm/service_usage.go b/service/ccm/service_usage.go index 53ae4658..7d39e3ce 100644 --- a/service/ccm/service_usage.go +++ b/service/ccm/service_usage.go @@ -124,12 +124,69 @@ var ( CacheWritePrice: 3.75, } + opus45Pricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice: 6.25, + } + + sonnet45StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice: 3.75, + } + + sonnet45PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice: 7.5, + } + + haiku45Pricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice: 1.25, + } + + haiku3Pricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 1.25, + CacheReadPrice: 0.03, + CacheWritePrice: 0.3, + } + + opus3Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice: 18.75, + } + modelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^claude-opus-4-5-`), + standardPricing: opus45Pricing, + premiumPricing: nil, + }, { pattern: regexp.MustCompile(`^claude-(?:opus-4-|4-opus-|opus-4-1-)`), standardPricing: opus4Pricing, premiumPricing: nil, }, + { + pattern: regexp.MustCompile(`^claude-(?:opus-3-|3-opus-)`), + standardPricing: opus3Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5-|4-5-sonnet-)`), + standardPricing: sonnet45StandardPricing, + premiumPricing: &sonnet45PremiumPricing, + }, { pattern: regexp.MustCompile(`^claude-3-7-sonnet-`), standardPricing: sonnet4StandardPricing, @@ -140,6 +197,16 @@ var ( standardPricing: sonnet4StandardPricing, premiumPricing: &sonnet4PremiumPricing, }, + { + pattern: regexp.MustCompile(`^claude-3-5-sonnet-`), + standardPricing: sonnet35Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:haiku-4-5-|4-5-haiku-)`), + standardPricing: haiku45Pricing, + premiumPricing: nil, + }, { pattern: regexp.MustCompile(`^claude-haiku-4-`), standardPricing: haiku4Pricing, @@ -151,8 +218,8 @@ var ( premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-5-sonnet-`), - standardPricing: sonnet35Pricing, + pattern: regexp.MustCompile(`^claude-3-haiku-`), + standardPricing: haiku3Pricing, premiumPricing: nil, }, } From e8620587dd67650f5d5a5a1e63d88aed3323751e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 16 Dec 2025 22:09:02 +0800 Subject: [PATCH 053/185] Add OpenAI Codex Multiplexer service --- .github/workflows/build.yml | 16 +- .github/workflows/docker.yml | 2 +- .github/workflows/linux.yml | 2 +- Dockerfile | 2 +- Makefile | 2 +- constant/proxy.go | 1 + docs/configuration/service/index.md | 1 + docs/configuration/service/index.zh.md | 1 + docs/configuration/service/ocm.md | 171 ++++++++ docs/configuration/service/ocm.zh.md | 171 ++++++++ go.mod | 1 + go.sum | 2 + include/ocm.go | 12 + include/ocm_stub.go | 20 + include/registry.go | 1 + mkdocs.yml | 2 + option/ocm.go | 20 + release/local/common.sh | 2 +- service/ocm/credential.go | 173 ++++++++ service/ocm/credential_darwin.go | 25 ++ service/ocm/credential_other.go | 25 ++ service/ocm/service.go | 555 +++++++++++++++++++++++++ service/ocm/service_usage.go | 445 ++++++++++++++++++++ service/ocm/service_user.go | 29 ++ 24 files changed, 1673 insertions(+), 8 deletions(-) create mode 100644 docs/configuration/service/ocm.md create mode 100644 docs/configuration/service/ocm.zh.md create mode 100644 include/ocm.go create mode 100644 include/ocm_stub.go create mode 100644 option/ocm.go create mode 100644 service/ocm/credential.go create mode 100644 service/ocm/credential_darwin.go create mode 100644 service/ocm/credential_other.go create mode 100644 service/ocm/service.go create mode 100644 service/ocm/service_usage.go create mode 100644 service/ocm/service_user.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 635642b7..ac4b9ec4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,7 +190,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then TAGS="${TAGS},with_naive_outbound" fi @@ -427,7 +427,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then TAGS="${TAGS},with_naive_outbound" fi @@ -495,7 +495,7 @@ jobs: - name: Build run: | mkdir -p dist - go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` + go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" ` ./cmd/sing-box env: @@ -885,6 +885,16 @@ jobs: with: path: dist merge-multiple: true + - name: Generate SFA version metadata + run: |- + VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2) + cat > dist/SFA-version-metadata.json << EOF + { + "version_code": ${VERSION_CODE}, + "version_name": "${VERSION}" + } + EOF + cat dist/SFA-version-metadata.json - name: Upload builds if: ${{ env.PUBLISHED == 'false' }} run: |- diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e4041b5b..5447457e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -93,7 +93,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then TAGS="${TAGS},with_naive_outbound,with_musl" fi diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f755f2df..5b60f4e7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -116,7 +116,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then TAGS="${TAGS},with_naive_outbound,with_musl" fi diff --git a/Dockerfile b/Dockerfile index 5162d461..fb39e8b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN set -ex \ && export COMMIT=$(git rev-parse --short HEAD) \ && export VERSION=$(go run ./cmd/internal/read_tag) \ && go build -v -trimpath -tags \ - "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" \ + "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \ -o /go/bin/sing-box \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box diff --git a/Makefile b/Makefile index c5e2d8ff..bd70837e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0 +TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0 GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) diff --git a/constant/proxy.go b/constant/proxy.go index a54a3a75..a5193623 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -29,6 +29,7 @@ const ( TypeResolved = "resolved" TypeSSMAPI = "ssm-api" TypeCCM = "ccm" + TypeOCM = "ocm" ) const ( diff --git a/docs/configuration/service/index.md b/docs/configuration/service/index.md index 2bd1a4a3..de3583b2 100644 --- a/docs/configuration/service/index.md +++ b/docs/configuration/service/index.md @@ -25,6 +25,7 @@ icon: material/new-box |------------|------------------------| | `ccm` | [CCM](./ccm) | | `derp` | [DERP](./derp) | +| `ocm` | [OCM](./ocm) | | `resolved` | [Resolved](./resolved) | | `ssm-api` | [SSM API](./ssm-api) | diff --git a/docs/configuration/service/index.zh.md b/docs/configuration/service/index.zh.md index b4a73eda..a0d18cbb 100644 --- a/docs/configuration/service/index.zh.md +++ b/docs/configuration/service/index.zh.md @@ -25,6 +25,7 @@ icon: material/new-box |-----------|------------------------| | `ccm` | [CCM](./ccm) | | `derp` | [DERP](./derp) | +| `ocm` | [OCM](./ocm) | | `resolved`| [Resolved](./resolved) | | `ssm-api` | [SSM API](./ssm-api) | diff --git a/docs/configuration/service/ocm.md b/docs/configuration/service/ocm.md new file mode 100644 index 00000000..59dba7da --- /dev/null +++ b/docs/configuration/service/ocm.md @@ -0,0 +1,171 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.13.0" + +# OCM + +OCM (OpenAI Codex Multiplexer) service is a multiplexing service that allows you to access your local OpenAI Codex subscription remotely through custom tokens. + +It handles OAuth authentication with OpenAI's API on your local machine while allowing remote clients to authenticate using custom tokens. + +### Structure + +```json +{ + "type": "ocm", + + ... // Listen Fields + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### credential_path + +Path to the OpenAI OAuth credentials file. + +If not specified, defaults to `~/.codex/auth.json`. + +Refreshed tokens are automatically written back to the same location. + +#### usages_path + +Path to the file for storing aggregated API usage statistics. + +Usage tracking is disabled if not specified. + +When enabled, the service tracks and saves comprehensive statistics including: +- Request counts +- Token usage (input, output, cached) +- Calculated costs in USD based on OpenAI API pricing + +Statistics are organized by model and optionally by user when authentication is enabled. + +The statistics file is automatically saved every minute and upon service shutdown. + +#### users + +List of authorized users for token authentication. + +If empty, no authentication is required. + +Object format: + +```json +{ + "name": "", + "token": "" +} +``` + +Object fields: + +- `name`: Username identifier for tracking purposes. +- `token`: Bearer token for authentication. Clients authenticate by setting the `Authorization: Bearer ` header. + +#### headers + +Custom HTTP headers to send to the OpenAI API. + +These headers will override any existing headers with the same name. + +#### detour + +Outbound tag for connecting to the OpenAI API. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +### Example + +#### Server + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +#### Client + +Add to `~/.codex/config.toml`: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +``` + +Then run: + +```bash +codex --model-provider ocm +``` + +### Example with Authentication + +#### Server + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./codex-usages.json", + "users": [ + { + "name": "alice", + "token": "sk-alice-secret-token" + }, + { + "name": "bob", + "token": "sk-bob-secret-token" + } + ] + } + ] +} +``` + +#### Client + +Add to `~/.codex/config.toml`: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +experimental_bearer_token = "sk-alice-secret-token" +``` + +Then run: + +```bash +codex --model-provider ocm +``` diff --git a/docs/configuration/service/ocm.zh.md b/docs/configuration/service/ocm.zh.md new file mode 100644 index 00000000..ee1d8510 --- /dev/null +++ b/docs/configuration/service/ocm.zh.md @@ -0,0 +1,171 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.13.0 起" + +# OCM + +OCM(OpenAI Codex 多路复用器)服务是一个多路复用服务,允许您通过自定义令牌远程访问本地的 OpenAI Codex 订阅。 + +它在本地机器上处理与 OpenAI API 的 OAuth 身份验证,同时允许远程客户端使用自定义令牌进行身份验证。 + +### 结构 + +```json +{ + "type": "ocm", + + ... // 监听字段 + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### credential_path + +OpenAI OAuth 凭据文件的路径。 + +如果未指定,默认值为 `~/.codex/auth.json`。 + +刷新的令牌会自动写回相同位置。 + +#### usages_path + +用于存储聚合 API 使用统计信息的文件路径。 + +如果未指定,使用跟踪将被禁用。 + +启用后,服务会跟踪并保存全面的统计信息,包括: +- 请求计数 +- 令牌使用量(输入、输出、缓存) +- 基于 OpenAI API 定价计算的美元成本 + +统计信息按模型以及可选的用户(启用身份验证时)进行组织。 + +统计文件每分钟自动保存一次,并在服务关闭时保存。 + +#### users + +用于令牌身份验证的授权用户列表。 + +如果为空,则不需要身份验证。 + +对象格式: + +```json +{ + "name": "", + "token": "" +} +``` + +对象字段: + +- `name`:用于跟踪的用户名标识符。 +- `token`:用于身份验证的 Bearer 令牌。客户端通过设置 `Authorization: Bearer ` 头进行身份验证。 + +#### headers + +发送到 OpenAI API 的自定义 HTTP 头。 + +这些头会覆盖同名的现有头。 + +#### detour + +用于连接 OpenAI API 的出站标签。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 + +### 示例 + +#### 服务端 + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +#### 客户端 + +在 `~/.codex/config.toml` 中添加: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +``` + +然后运行: + +```bash +codex --model-provider ocm +``` + +### 带身份验证的示例 + +#### 服务端 + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./codex-usages.json", + "users": [ + { + "name": "alice", + "token": "sk-alice-secret-token" + }, + { + "name": "bob", + "token": "sk-bob-secret-token" + } + ] + } + ] +} +``` + +#### 客户端 + +在 `~/.codex/config.toml` 中添加: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +experimental_bearer_token = "sk-alice-secret-token" +``` + +然后运行: + +```bash +codex --model-provider ocm +``` diff --git a/go.mod b/go.mod index a0b13a0a..61d8deef 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/metacubex/utls v1.8.4 github.com/mholt/acmez/v3 v3.1.2 github.com/miekg/dns v1.1.67 + github.com/openai/openai-go/v3 v3.13.0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a diff --git a/go.sum b/go.sum index 84efb47e..789fa8d8 100644 --- a/go.sum +++ b/go.sum @@ -131,6 +131,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q= +github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= diff --git a/include/ocm.go b/include/ocm.go new file mode 100644 index 00000000..cdea9eea --- /dev/null +++ b/include/ocm.go @@ -0,0 +1,12 @@ +//go:build with_ocm + +package include + +import ( + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/service/ocm" +) + +func registerOCMService(registry *service.Registry) { + ocm.RegisterService(registry) +} diff --git a/include/ocm_stub.go b/include/ocm_stub.go new file mode 100644 index 00000000..d5a94fcb --- /dev/null +++ b/include/ocm_stub.go @@ -0,0 +1,20 @@ +//go:build !with_ocm + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerOCMService(registry *service.Registry) { + service.Register[option.OCMServiceOptions](registry, C.TypeOCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) { + return nil, E.New(`OCM is not included in this build, rebuild with -tags with_ocm`) + }) +} diff --git a/include/registry.go b/include/registry.go index 8f08189d..d909b850 100644 --- a/include/registry.go +++ b/include/registry.go @@ -136,6 +136,7 @@ func ServiceRegistry() *service.Registry { registerDERPService(registry) registerCCMService(registry) + registerOCMService(registry) return registry } diff --git a/mkdocs.yml b/mkdocs.yml index c49bfa2a..a505f3e4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -176,6 +176,8 @@ nav: - DERP: configuration/service/derp.md - Resolved: configuration/service/resolved.md - SSM API: configuration/service/ssm-api.md + - CCM: configuration/service/ccm.md + - OCM: configuration/service/ocm.md markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets diff --git a/option/ocm.go b/option/ocm.go new file mode 100644 index 00000000..c13a1c1f --- /dev/null +++ b/option/ocm.go @@ -0,0 +1,20 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type OCMServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + CredentialPath string `json:"credential_path,omitempty"` + Users []OCMUser `json:"users,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + Detour string `json:"detour,omitempty"` + UsagesPath string `json:"usages_path,omitempty"` +} + +type OCMUser struct { + Name string `json:"name,omitempty"` + Token string `json:"token,omitempty"` +} diff --git a/release/local/common.sh b/release/local/common.sh index d24bba47..68a494ba 100755 --- a/release/local/common.sh +++ b/release/local/common.sh @@ -11,7 +11,7 @@ INSTALL_CONFIG_PATH="/usr/local/etc/sing-box" INSTALL_DATA_PATH="/var/lib/sing-box" SYSTEMD_SERVICE_PATH="/etc/systemd/system" -DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" +DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" setup_environment() { if [ -d /usr/local/go ]; then diff --git a/service/ocm/credential.go b/service/ocm/credential.go new file mode 100644 index 00000000..76651a8e --- /dev/null +++ b/service/ocm/credential.go @@ -0,0 +1,173 @@ +package ocm + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "os" + "os/user" + "path/filepath" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + oauth2ClientID = "app_EMoamEEZ73f0CkXaXp7hrann" + oauth2TokenURL = "https://auth.openai.com/oauth/token" + openaiAPIBaseURL = "https://api.openai.com" + chatGPTBackendURL = "https://chatgpt.com/backend-api/codex" + tokenRefreshIntervalDays = 8 +) + +func getRealUser() (*user.User, error) { + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + sudoUserInfo, err := user.Lookup(sudoUser) + if err == nil { + return sudoUserInfo, nil + } + } + return user.Current() +} + +func getDefaultCredentialsPath() (string, error) { + if codexHome := os.Getenv("CODEX_HOME"); codexHome != "" { + return filepath.Join(codexHome, "auth.json"), nil + } + userInfo, err := getRealUser() + if err != nil { + return "", err + } + return filepath.Join(userInfo.HomeDir, ".codex", "auth.json"), nil +} + +func readCredentialsFromFile(path string) (*oauthCredentials, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var credentials oauthCredentials + err = json.Unmarshal(data, &credentials) + if err != nil { + return nil, err + } + return &credentials, nil +} + +func writeCredentialsToFile(credentials *oauthCredentials, path string) error { + data, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0o600) +} + +type oauthCredentials struct { + APIKey string `json:"OPENAI_API_KEY,omitempty"` + Tokens *tokenData `json:"tokens,omitempty"` + LastRefresh *time.Time `json:"last_refresh,omitempty"` +} + +type tokenData struct { + IDToken string `json:"id_token,omitempty"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + AccountID string `json:"account_id,omitempty"` +} + +func (c *oauthCredentials) isAPIKeyMode() bool { + return c.APIKey != "" +} + +func (c *oauthCredentials) getAccessToken() string { + if c.APIKey != "" { + return c.APIKey + } + if c.Tokens != nil { + return c.Tokens.AccessToken + } + return "" +} + +func (c *oauthCredentials) getAccountID() string { + if c.Tokens != nil { + return c.Tokens.AccountID + } + return "" +} + +func (c *oauthCredentials) needsRefresh() bool { + if c.APIKey != "" { + return false + } + if c.Tokens == nil || c.Tokens.RefreshToken == "" { + return false + } + if c.LastRefresh == nil { + return true + } + return time.Since(*c.LastRefresh) >= time.Duration(tokenRefreshIntervalDays)*24*time.Hour +} + +func refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) { + if credentials.Tokens == nil || credentials.Tokens.RefreshToken == "" { + return nil, E.New("refresh token is empty") + } + + requestBody, err := json.Marshal(map[string]string{ + "grant_type": "refresh_token", + "refresh_token": credentials.Tokens.RefreshToken, + "client_id": oauth2ClientID, + "scope": "openid profile email", + }) + if err != nil { + return nil, E.Cause(err, "marshal request") + } + + request, err := http.NewRequest("POST", oauth2TokenURL, bytes.NewReader(requestBody)) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + + response, err := httpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + body, _ := io.ReadAll(response.Body) + return nil, E.New("refresh failed: ", response.Status, " ", string(body)) + } + + var tokenResponse struct { + IDToken string `json:"id_token"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + } + err = json.NewDecoder(response.Body).Decode(&tokenResponse) + if err != nil { + return nil, E.Cause(err, "decode response") + } + + newCredentials := *credentials + if newCredentials.Tokens == nil { + newCredentials.Tokens = &tokenData{} + } + if tokenResponse.IDToken != "" { + newCredentials.Tokens.IDToken = tokenResponse.IDToken + } + if tokenResponse.AccessToken != "" { + newCredentials.Tokens.AccessToken = tokenResponse.AccessToken + } + if tokenResponse.RefreshToken != "" { + newCredentials.Tokens.RefreshToken = tokenResponse.RefreshToken + } + now := time.Now() + newCredentials.LastRefresh = &now + + return &newCredentials, nil +} diff --git a/service/ocm/credential_darwin.go b/service/ocm/credential_darwin.go new file mode 100644 index 00000000..f3da2a63 --- /dev/null +++ b/service/ocm/credential_darwin.go @@ -0,0 +1,25 @@ +//go:build darwin + +package ocm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(credentials, customPath) +} diff --git a/service/ocm/credential_other.go b/service/ocm/credential_other.go new file mode 100644 index 00000000..22dfd033 --- /dev/null +++ b/service/ocm/credential_other.go @@ -0,0 +1,25 @@ +//go:build !darwin + +package ocm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(credentials, customPath) +} diff --git a/service/ocm/service.go b/service/ocm/service.go new file mode 100644 index 00000000..e8f95410 --- /dev/null +++ b/service/ocm/service.go @@ -0,0 +1,555 @@ +package ocm + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "mime" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" + "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/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + + "github.com/go-chi/chi/v5" + "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/responses" + "golang.org/x/net/http2" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.OCMServiceOptions](registry, C.TypeOCM, NewService) +} + +type errorResponse struct { + Error errorDetails `json:"error"` +} + +type errorDetails struct { + Type string `json:"type"` + Code string `json:"code,omitempty"` + Message string `json:"message"` +} + +func writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + json.NewEncoder(w).Encode(errorResponse{ + Error: errorDetails{ + Type: errorType, + Message: message, + }, + }) +} + +func isHopByHopHeader(header string) bool { + switch strings.ToLower(header) { + case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host": + return true + default: + return false + } +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger log.ContextLogger + credentialPath string + credentials *oauthCredentials + users []option.OCMUser + httpClient *http.Client + httpHeaders http.Header + listener *listener.Listener + tlsConfig tls.ServerConfig + httpServer *http.Server + userManager *UserManager + accessMutex sync.RWMutex + usageTracker *AggregatedUsage + trackingGroup sync.WaitGroup + shuttingDown bool +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) { + serviceDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: option.DialerOptions{ + Detour: options.Detour, + }, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "create dialer") + } + + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + + userManager := &UserManager{ + tokenMap: make(map[string]string), + } + + var usageTracker *AggregatedUsage + if options.UsagesPath != "" { + usageTracker = &AggregatedUsage{ + LastUpdated: time.Now(), + Combinations: make([]CostCombination, 0), + filePath: options.UsagesPath, + logger: logger, + } + } + + service := &Service{ + Adapter: boxService.NewAdapter(C.TypeOCM, tag), + ctx: ctx, + logger: logger, + credentialPath: options.CredentialPath, + users: options.Users, + httpClient: httpClient, + httpHeaders: options.Headers.Build(), + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + userManager: userManager, + usageTracker: usageTracker, + } + + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + service.tlsConfig = tlsConfig + } + + return service, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + s.userManager.UpdateUsers(s.users) + + credentials, err := platformReadCredentials(s.credentialPath) + if err != nil { + return E.Cause(err, "read credentials") + } + s.credentials = credentials + + if s.usageTracker != nil { + err = s.usageTracker.Load() + if err != nil { + s.logger.Warn("load usage statistics: ", err) + } + } + + router := chi.NewRouter() + router.Mount("/", s) + + s.httpServer = &http.Server{Handler: router} + + if s.tlsConfig != nil { + err = s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + + tcpListener, err := s.listener.ListenTCP() + if err != nil { + return err + } + + if s.tlsConfig != nil { + if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { + s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) + } + + go func() { + serveErr := s.httpServer.Serve(tcpListener) + if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { + s.logger.Error("serve error: ", serveErr) + } + }() + + return nil +} + +func (s *Service) getAccessToken() (string, error) { + s.accessMutex.RLock() + if !s.credentials.needsRefresh() { + token := s.credentials.getAccessToken() + s.accessMutex.RUnlock() + return token, nil + } + s.accessMutex.RUnlock() + + s.accessMutex.Lock() + defer s.accessMutex.Unlock() + + if !s.credentials.needsRefresh() { + return s.credentials.getAccessToken(), nil + } + + newCredentials, err := refreshToken(s.httpClient, s.credentials) + if err != nil { + return "", err + } + + s.credentials = newCredentials + + err = platformWriteCredentials(newCredentials, s.credentialPath) + if err != nil { + s.logger.Warn("persist refreshed token: ", err) + } + + return newCredentials.getAccessToken(), nil +} + +func (s *Service) getAccountID() string { + s.accessMutex.RLock() + defer s.accessMutex.RUnlock() + return s.credentials.getAccountID() +} + +func (s *Service) isAPIKeyMode() bool { + s.accessMutex.RLock() + defer s.accessMutex.RUnlock() + return s.credentials.isAPIKeyMode() +} + +func (s *Service) getBaseURL() string { + if s.isAPIKeyMode() { + return openaiAPIBaseURL + } + return chatGPTBackendURL +} + +func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + if !strings.HasPrefix(path, "/v1/") { + writeJSONError(w, r, http.StatusNotFound, "invalid_request_error", "path must start with /v1/") + return + } + + var proxyPath string + if s.isAPIKeyMode() { + proxyPath = path + } else { + if path == "/v1/chat/completions" { + writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error", + "chat completions endpoint is only available in API key mode") + return + } + proxyPath = strings.TrimPrefix(path, "/v1") + } + + var username string + if len(s.users) > 0 { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": missing Authorization header") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") + return + } + clientToken := strings.TrimPrefix(authHeader, "Bearer ") + if clientToken == authHeader { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": invalid Authorization format") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") + return + } + var ok bool + username, ok = s.userManager.Authenticate(clientToken) + if !ok { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": unknown key: ", clientToken) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") + return + } + } + + var requestModel string + + if s.usageTracker != nil && r.Body != nil { + bodyBytes, err := io.ReadAll(r.Body) + if err == nil { + var request struct { + Model string `json:"model"` + } + err := json.Unmarshal(bodyBytes, &request) + if err == nil { + requestModel = request.Model + } + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + } + + accessToken, err := s.getAccessToken() + if err != nil { + s.logger.Error("get access token: ", err) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "Authentication failed") + return + } + + proxyURL := s.getBaseURL() + proxyPath + if r.URL.RawQuery != "" { + proxyURL += "?" + r.URL.RawQuery + } + proxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body) + if err != nil { + s.logger.Error("create proxy request: ", err) + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "Internal server error") + return + } + + for key, values := range r.Header { + if !isHopByHopHeader(key) && key != "Authorization" { + proxyRequest.Header[key] = values + } + } + + for key, values := range s.httpHeaders { + proxyRequest.Header.Del(key) + proxyRequest.Header[key] = values + } + + proxyRequest.Header.Set("Authorization", "Bearer "+accessToken) + + if accountID := s.getAccountID(); accountID != "" { + proxyRequest.Header.Set("ChatGPT-Account-Id", accountID) + } + + response, err := s.httpClient.Do(proxyRequest) + if err != nil { + writeJSONError(w, r, http.StatusBadGateway, "api_error", err.Error()) + return + } + defer response.Body.Close() + + for key, values := range response.Header { + if !isHopByHopHeader(key) { + w.Header()[key] = values + } + } + w.WriteHeader(response.StatusCode) + + trackUsage := s.usageTracker != nil && response.StatusCode == http.StatusOK && + (path == "/v1/chat/completions" || strings.HasPrefix(path, "/v1/responses")) + if trackUsage { + s.handleResponseWithTracking(w, response, path, requestModel, username) + } else { + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + if err == nil && mediaType != "text/event-stream" { + _, _ = io.Copy(w, response.Body) + return + } + flusher, ok := w.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + buffer := make([]byte, buf.BufferSize) + for { + n, err := response.Body.Read(buffer) + if n > 0 { + _, writeError := w.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + if err != nil { + return + } + } + } +} + +func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) { + isChatCompletions := path == "/v1/chat/completions" + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + isStreaming := err == nil && mediaType == "text/event-stream" + + if !isStreaming { + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + s.logger.Error("read response body: ", err) + return + } + + var responseModel string + var inputTokens, outputTokens, cachedTokens int64 + + if isChatCompletions { + var chatCompletion openai.ChatCompletion + if json.Unmarshal(bodyBytes, &chatCompletion) == nil { + responseModel = chatCompletion.Model + inputTokens = chatCompletion.Usage.PromptTokens + outputTokens = chatCompletion.Usage.CompletionTokens + cachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens + } + } else { + var responsesResponse responses.Response + if json.Unmarshal(bodyBytes, &responsesResponse) == nil { + responseModel = string(responsesResponse.Model) + inputTokens = responsesResponse.Usage.InputTokens + outputTokens = responsesResponse.Usage.OutputTokens + cachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens + } + } + + if inputTokens > 0 || outputTokens > 0 { + if responseModel == "" { + responseModel = requestModel + } + if responseModel != "" { + s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + } + } + + _, _ = writer.Write(bodyBytes) + return + } + + flusher, ok := writer.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + + var inputTokens, outputTokens, cachedTokens int64 + var responseModel string + buffer := make([]byte, buf.BufferSize) + var leftover []byte + + for { + n, err := response.Body.Read(buffer) + if n > 0 { + data := append(leftover, buffer[:n]...) + lines := bytes.Split(data, []byte("\n")) + + if err == nil { + leftover = lines[len(lines)-1] + lines = lines[:len(lines)-1] + } else { + leftover = nil + } + + for _, line := range lines { + line = bytes.TrimSpace(line) + if len(line) == 0 { + continue + } + + if bytes.HasPrefix(line, []byte("data: ")) { + eventData := bytes.TrimPrefix(line, []byte("data: ")) + if bytes.Equal(eventData, []byte("[DONE]")) { + continue + } + + if isChatCompletions { + var chatChunk openai.ChatCompletionChunk + if json.Unmarshal(eventData, &chatChunk) == nil { + if chatChunk.Model != "" { + responseModel = chatChunk.Model + } + if chatChunk.Usage.PromptTokens > 0 { + inputTokens = chatChunk.Usage.PromptTokens + cachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens + } + if chatChunk.Usage.CompletionTokens > 0 { + outputTokens = chatChunk.Usage.CompletionTokens + } + } + } else { + var streamEvent responses.ResponseStreamEventUnion + if json.Unmarshal(eventData, &streamEvent) == nil { + if streamEvent.Type == "response.completed" { + completedEvent := streamEvent.AsResponseCompleted() + if string(completedEvent.Response.Model) != "" { + responseModel = string(completedEvent.Response.Model) + } + if completedEvent.Response.Usage.InputTokens > 0 { + inputTokens = completedEvent.Response.Usage.InputTokens + cachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens + } + if completedEvent.Response.Usage.OutputTokens > 0 { + outputTokens = completedEvent.Response.Usage.OutputTokens + } + } + } + } + } + } + + _, writeError := writer.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + + if err != nil { + if responseModel == "" { + responseModel = requestModel + } + + if inputTokens > 0 || outputTokens > 0 { + if responseModel != "" { + s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + } + } + return + } + } +} + +func (s *Service) Close() error { + err := common.Close( + common.PtrOrNil(s.httpServer), + common.PtrOrNil(s.listener), + s.tlsConfig, + ) + + if s.usageTracker != nil { + s.usageTracker.cancelPendingSave() + saveErr := s.usageTracker.Save() + if saveErr != nil { + s.logger.Error("save usage statistics: ", saveErr) + } + } + + return err +} diff --git a/service/ocm/service_usage.go b/service/ocm/service_usage.go new file mode 100644 index 00000000..7089f4d3 --- /dev/null +++ b/service/ocm/service_usage.go @@ -0,0 +1,445 @@ +package ocm + +import ( + "encoding/json" + "math" + "os" + "regexp" + "sync" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" +) + +type UsageStats struct { + RequestCount int `json:"request_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CachedTokens int64 `json:"cached_tokens"` +} + +func (u *UsageStats) UnmarshalJSON(data []byte) error { + type Alias UsageStats + aux := &struct { + *Alias + PromptTokens int64 `json:"prompt_tokens"` + CompletionTokens int64 `json:"completion_tokens"` + }{ + Alias: (*Alias)(u), + } + err := json.Unmarshal(data, aux) + if err != nil { + return err + } + if u.InputTokens == 0 && aux.PromptTokens > 0 { + u.InputTokens = aux.PromptTokens + } + if u.OutputTokens == 0 && aux.CompletionTokens > 0 { + u.OutputTokens = aux.CompletionTokens + } + return nil +} + +type CostCombination struct { + Model string `json:"model"` + Total UsageStats `json:"total"` + ByUser map[string]UsageStats `json:"by_user"` +} + +type AggregatedUsage struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + mutex sync.Mutex + filePath string + logger log.ContextLogger + lastSaveTime time.Time + pendingSave bool + saveTimer *time.Timer + saveMutex sync.Mutex +} + +type UsageStatsJSON struct { + RequestCount int `json:"request_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CachedTokens int64 `json:"cached_tokens"` + CostUSD float64 `json:"cost_usd"` +} + +type CostCombinationJSON struct { + Model string `json:"model"` + Total UsageStatsJSON `json:"total"` + ByUser map[string]UsageStatsJSON `json:"by_user"` +} + +type CostsSummaryJSON struct { + TotalUSD float64 `json:"total_usd"` + ByUser map[string]float64 `json:"by_user"` +} + +type AggregatedUsageJSON struct { + LastUpdated time.Time `json:"last_updated"` + Costs CostsSummaryJSON `json:"costs"` + Combinations []CostCombinationJSON `json:"combinations"` +} + +type ModelPricing struct { + InputPrice float64 + OutputPrice float64 + CachedInputPrice float64 +} + +type modelFamily struct { + pattern *regexp.Regexp + pricing ModelPricing +} + +var ( + gpt4oPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 10.0, + CachedInputPrice: 1.25, + } + + gpt4oMiniPricing = ModelPricing{ + InputPrice: 0.15, + OutputPrice: 0.6, + CachedInputPrice: 0.075, + } + + gpt4oAudioPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 10.0, + CachedInputPrice: 1.25, + } + + o1Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 60.0, + CachedInputPrice: 7.5, + } + + o1MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + o3MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + o3Pricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 1.0, + } + + o4MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + gpt41Pricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + gpt41MiniPricing = ModelPricing{ + InputPrice: 0.4, + OutputPrice: 1.6, + CachedInputPrice: 0.1, + } + + gpt41NanoPricing = ModelPricing{ + InputPrice: 0.1, + OutputPrice: 0.4, + CachedInputPrice: 0.025, + } + + modelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-4\.1-nano`), + pricing: gpt41NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini`), + pricing: gpt41MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1`), + pricing: gpt41Pricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini`), + pricing: o4MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o3-mini`), + pricing: o3MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o3`), + pricing: o3Pricing, + }, + { + pattern: regexp.MustCompile(`^o1-mini`), + pricing: o1MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o1`), + pricing: o1Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-audio`), + pricing: gpt4oAudioPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini`), + pricing: gpt4oMiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o`), + pricing: gpt4oPricing, + }, + { + pattern: regexp.MustCompile(`^chatgpt-4o`), + pricing: gpt4oPricing, + }, + } +) + +func getPricing(model string) ModelPricing { + for _, family := range modelFamilies { + if family.pattern.MatchString(model) { + return family.pricing + } + } + return gpt4oPricing +} + +func calculateCost(stats UsageStats, model string) float64 { + pricing := getPricing(model) + + regularInputTokens := stats.InputTokens - stats.CachedTokens + if regularInputTokens < 0 { + regularInputTokens = 0 + } + + cost := (float64(regularInputTokens)*pricing.InputPrice + + float64(stats.OutputTokens)*pricing.OutputPrice + + float64(stats.CachedTokens)*pricing.CachedInputPrice) / 1_000_000 + + return math.Round(cost*100) / 100 +} + +func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { + u.mutex.Lock() + defer u.mutex.Unlock() + + result := &AggregatedUsageJSON{ + LastUpdated: u.LastUpdated, + Combinations: make([]CostCombinationJSON, len(u.Combinations)), + Costs: CostsSummaryJSON{ + TotalUSD: 0, + ByUser: make(map[string]float64), + }, + } + + for i, combo := range u.Combinations { + totalCost := calculateCost(combo.Total, combo.Model) + + result.Costs.TotalUSD += totalCost + + comboJSON := CostCombinationJSON{ + Model: combo.Model, + Total: UsageStatsJSON{ + RequestCount: combo.Total.RequestCount, + InputTokens: combo.Total.InputTokens, + OutputTokens: combo.Total.OutputTokens, + CachedTokens: combo.Total.CachedTokens, + CostUSD: totalCost, + }, + ByUser: make(map[string]UsageStatsJSON), + } + + for user, userStats := range combo.ByUser { + userCost := calculateCost(userStats, combo.Model) + result.Costs.ByUser[user] += userCost + + comboJSON.ByUser[user] = UsageStatsJSON{ + RequestCount: userStats.RequestCount, + InputTokens: userStats.InputTokens, + OutputTokens: userStats.OutputTokens, + CachedTokens: userStats.CachedTokens, + CostUSD: userCost, + } + } + + result.Combinations[i] = comboJSON + } + + result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100 + for user, cost := range result.Costs.ByUser { + result.Costs.ByUser[user] = math.Round(cost*100) / 100 + } + + return result +} + +func (u *AggregatedUsage) Load() error { + u.mutex.Lock() + defer u.mutex.Unlock() + + data, err := os.ReadFile(u.filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + var temp struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + } + + err = json.Unmarshal(data, &temp) + if err != nil { + return err + } + + u.LastUpdated = temp.LastUpdated + u.Combinations = temp.Combinations + + for i := range u.Combinations { + if u.Combinations[i].ByUser == nil { + u.Combinations[i].ByUser = make(map[string]UsageStats) + } + } + + return nil +} + +func (u *AggregatedUsage) Save() error { + jsonData := u.ToJSON() + + data, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return err + } + + tmpFile := u.filePath + ".tmp" + err = os.WriteFile(tmpFile, data, 0o644) + if err != nil { + return err + } + defer os.Remove(tmpFile) + err = os.Rename(tmpFile, u.filePath) + if err == nil { + u.saveMutex.Lock() + u.lastSaveTime = time.Now() + u.saveMutex.Unlock() + } + return err +} + +func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, user string) error { + if model == "" { + return E.New("model cannot be empty") + } + + u.mutex.Lock() + defer u.mutex.Unlock() + + u.LastUpdated = time.Now() + + var combo *CostCombination + for i := range u.Combinations { + if u.Combinations[i].Model == model { + combo = &u.Combinations[i] + break + } + } + + if combo == nil { + newCombo := CostCombination{ + Model: model, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), + } + u.Combinations = append(u.Combinations, newCombo) + combo = &u.Combinations[len(u.Combinations)-1] + } + + combo.Total.RequestCount++ + combo.Total.InputTokens += inputTokens + combo.Total.OutputTokens += outputTokens + combo.Total.CachedTokens += cachedTokens + + if user != "" { + userStats := combo.ByUser[user] + userStats.RequestCount++ + userStats.InputTokens += inputTokens + userStats.OutputTokens += outputTokens + userStats.CachedTokens += cachedTokens + combo.ByUser[user] = userStats + } + + go u.scheduleSave() + + return nil +} + +func (u *AggregatedUsage) scheduleSave() { + const saveInterval = time.Minute + + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + timeSinceLastSave := time.Since(u.lastSaveTime) + + if timeSinceLastSave >= saveInterval { + go u.saveAsync() + return + } + + if u.pendingSave { + return + } + + u.pendingSave = true + remainingTime := saveInterval - timeSinceLastSave + + u.saveTimer = time.AfterFunc(remainingTime, func() { + u.saveMutex.Lock() + u.pendingSave = false + u.saveMutex.Unlock() + u.saveAsync() + }) +} + +func (u *AggregatedUsage) saveAsync() { + err := u.Save() + if err != nil { + if u.logger != nil { + u.logger.Error("save usage statistics: ", err) + } + } +} + +func (u *AggregatedUsage) cancelPendingSave() { + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + if u.saveTimer != nil { + u.saveTimer.Stop() + u.saveTimer = nil + } + u.pendingSave = false +} diff --git a/service/ocm/service_user.go b/service/ocm/service_user.go new file mode 100644 index 00000000..494b981b --- /dev/null +++ b/service/ocm/service_user.go @@ -0,0 +1,29 @@ +package ocm + +import ( + "sync" + + "github.com/sagernet/sing-box/option" +) + +type UserManager struct { + accessMutex sync.RWMutex + tokenMap map[string]string +} + +func (m *UserManager) UpdateUsers(users []option.OCMUser) { + m.accessMutex.Lock() + defer m.accessMutex.Unlock() + tokenMap := make(map[string]string, len(users)) + for _, user := range users { + tokenMap[user.Token] = user.Name + } + m.tokenMap = tokenMap +} + +func (m *UserManager) Authenticate(token string) (string, bool) { + m.accessMutex.RLock() + username, found := m.tokenMap[token] + m.accessMutex.RUnlock() + return username, found +} From 8101a7b0bd633b0f9265e616b7f0b61129913df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 16 Dec 2025 22:15:47 +0800 Subject: [PATCH 054/185] Fix naiveproxy build --- .github/CRONET_GO_VERSION | 2 +- .github/workflows/build.yml | 71 +++++++++++---- docs/configuration/outbound/naive.md | 20 ++++- docs/configuration/outbound/naive.zh.md | 20 ++++- docs/installation/build-from-source.md | 24 ++++-- docs/installation/build-from-source.zh.md | 24 ++++-- go.mod | 50 +++++------ go.sum | 100 +++++++++++----------- test/go.mod | 46 +++++----- test/go.sum | 92 +++++++++++--------- 10 files changed, 278 insertions(+), 171 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 7616cee1..ad1bced9 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -fe7ab107d3a222ca878b9a727d76075938ee7cde +78951bf5e641fcb42bc82b73e70bd28d819e5e55 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac4b9ec4..7614ee16 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,11 +69,11 @@ jobs: strategy: matrix: include: - - { os: linux, arch: amd64, variant: purego, openwrt: "x86_64" } + - { os: linux, arch: amd64, variant: purego, naive: true, openwrt: "x86_64" } - { os: linux, arch: amd64, variant: glibc, naive: true } - { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - - { os: linux, arch: arm64, variant: purego, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } + - { os: linux, arch: arm64, variant: purego, naive: true, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - { os: linux, arch: arm64, variant: glibc, naive: true } - { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } @@ -217,6 +217,11 @@ jobs: GOMIPS: ${{ matrix.gomips }} GOMIPS64: ${{ matrix.gomips }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Extract libcronet.so + if: matrix.variant == 'purego' && matrix.naive + run: | + cd ~/cronet-go + CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist - name: Build (glibc) if: matrix.variant == 'glibc' run: | @@ -383,11 +388,14 @@ jobs: zip -r "${DIR_NAME}.zip" "${DIR_NAME}" else cp sing-box "${DIR_NAME}" + if [ -f libcronet.so ]; then + cp libcronet.so "${DIR_NAME}" + fi tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}" fi rm -r "${DIR_NAME}" - name: Cleanup - run: rm dist/sing-box + run: rm -f dist/sing-box dist/libcronet.so - name: Upload artifact uses: actions/upload-artifact@v4 with: @@ -476,9 +484,9 @@ jobs: strategy: matrix: include: - - { arch: amd64 } + - { arch: amd64, naive: true } - { arch: "386" } - - { arch: arm64 } + - { arch: arm64, naive: true } steps: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 @@ -493,6 +501,7 @@ jobs: git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV" git tag v${{ needs.calculate_version.outputs.version }} -f - name: Build + if: matrix.naive run: | mkdir -p dist go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` @@ -503,7 +512,36 @@ jobs: GOOS: windows GOARCH: ${{ matrix.arch }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build + if: ${{ !matrix.naive }} + run: | + mkdir -p dist + go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" ` + -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" ` + ./cmd/sing-box + env: + CGO_ENABLED: "0" + GOOS: windows + GOARCH: ${{ matrix.arch }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Extract libcronet.dll + if: matrix.naive + run: | + $CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION + $env:CGO_ENABLED = "0" + go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist - name: Archive + if: matrix.naive + run: | + $DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}" + mkdir "dist/$DIR_NAME" + Copy-Item LICENSE "dist/$DIR_NAME" + Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME" + Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME" + Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip" + Remove-Item -Recurse "dist/$DIR_NAME" + - name: Archive + if: ${{ !matrix.naive }} run: | $DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}" mkdir "dist/$DIR_NAME" @@ -512,6 +550,10 @@ jobs: Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip" Remove-Item -Recurse "dist/$DIR_NAME" - name: Cleanup + if: matrix.naive + run: Remove-Item dist/sing-box.exe, dist/libcronet.dll + - name: Cleanup + if: ${{ !matrix.naive }} run: Remove-Item dist/sing-box.exe - name: Upload artifact uses: actions/upload-artifact@v4 @@ -593,6 +635,15 @@ jobs: mkdir -p dist #cp clients/android/app/build/outputs/apk/play/release/*.apk dist cp clients/android/app/build/outputs/apk/other/release/*.apk dist + VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2) + VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2) + cat > dist/SFA-version-metadata.json << EOF + { + "version_code": ${VERSION_CODE}, + "version_name": "${VERSION_NAME}" + } + EOF + cat dist/SFA-version-metadata.json - name: Upload artifact uses: actions/upload-artifact@v4 with: @@ -885,16 +936,6 @@ jobs: with: path: dist merge-multiple: true - - name: Generate SFA version metadata - run: |- - VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2) - cat > dist/SFA-version-metadata.json << EOF - { - "version_code": ${VERSION_CODE}, - "version_name": "${VERSION}" - } - EOF - cat dist/SFA-version-metadata.json - name: Upload builds if: ${{ env.PUBLISHED == 'false' }} run: |- diff --git a/docs/configuration/outbound/naive.md b/docs/configuration/outbound/naive.md index ffcb3a4f..f1042492 100644 --- a/docs/configuration/outbound/naive.md +++ b/docs/configuration/outbound/naive.md @@ -24,9 +24,25 @@ icon: material/new-box } ``` -!!! warning "" +!!! warning "Platform Support" - NaiveProxy outbound is only available on Apple platforms, Android, Windows and some Linux architectures, see [Build from source](/installation/build-from-source/#with_naive_outbound). + NaiveProxy outbound is only available on Apple platforms, Android, Windows and certain Linux builds. + + **Official Release Build Variants:** + + | Build Variant | Platforms | Description | + |---------------|-----------|-------------| + | (default) | Linux amd64/arm64 | purego build with `libcronet.so` included | + | `-glibc` | Linux 386/amd64/arm/arm64 | CGO build dynamically linked with glibc, requires glibc >= 2.31 | + | `-musl` | Linux 386/amd64/arm/arm64 | CGO build statically linked with musl, no system requirements | + | (default) | Windows amd64/arm64 | purego build with `libcronet.dll` included | + + **Runtime Requirements:** + + - **Linux purego**: `libcronet.so` must be in the same directory as the sing-box binary or in system library path + - **Windows**: `libcronet.dll` must be in the same directory as `sing-box.exe` or in a directory listed in `PATH` + + For self-built binaries, see [Build from source](/installation/build-from-source/#with_naive_outbound). ### Fields diff --git a/docs/configuration/outbound/naive.zh.md b/docs/configuration/outbound/naive.zh.md index 0ed265be..26067473 100644 --- a/docs/configuration/outbound/naive.zh.md +++ b/docs/configuration/outbound/naive.zh.md @@ -24,9 +24,25 @@ icon: material/new-box } ``` -!!! warning "" +!!! warning "平台支持" - NaiveProxy 出站仅在 Apple 平台、Android、Windows 和部分架构的 Linux 上可用,参阅 [从源代码构建](/zh/installation/build-from-source/#with_naive_outbound)。 + NaiveProxy 出站仅在 Apple 平台、Android、Windows 和特定 Linux 构建上可用。 + + **官方发布版本区别:** + + | 构建变体 | 平台 | 说明 | + |-----------|------------------------|------------------------------------------| + | (默认) | Linux amd64/arm64 | purego 构建,包含 `libcronet.so` | + | `-glibc` | Linux 386/amd64/arm/arm64 | CGO 构建,动态链接 glibc,要求 glibc >= 2.31 | + | `-musl` | Linux 386/amd64/arm/arm64 | CGO 构建,静态链接 musl,无系统要求 | + | (默认) | Windows amd64/arm64 | purego 构建,包含 `libcronet.dll` | + + **运行时要求:** + + - **Linux purego**:`libcronet.so` 必须位于 sing-box 二进制文件相同目录或系统库路径中 + - **Windows**:`libcronet.dll` 必须位于 `sing-box.exe` 相同目录或 `PATH` 中的任意目录 + + 自行构建请参阅 [从源代码构建](/zh/installation/build-from-source/#with_naive_outbound)。 ### 字段 diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index b998cf7b..4d0e6370 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -68,26 +68,34 @@ NaiveProxy outbound requires special build configurations depending on your targ ### Supported Platforms -| Platform | Architectures | Mode | Requirements | -|-----------------|--------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------| -| Windows | * | purego | None | -| Linux | amd64, arm64 | purego | Download libcronet from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) to system library path or sing-box binary directory | -| Linux | 386, amd64, arm, arm64 | CGO | Chromium toolchain (see [cronet-go](https://github.com/sagernet/cronet-go)) | -| Apple platforms | * | CGO | Xcode | -| Android | * | CGO | Android NDK | +| Platform | Architectures | Mode | Requirements | +|-----------------|------------------------|--------|---------------------------------------------------| +| Linux | amd64, arm64 | purego | None (library included in official releases) | +| Linux | 386, amd64, arm, arm64 | CGO | Chromium toolchain, glibc >= 2.31 at runtime | +| Linux (musl) | 386, amd64, arm, arm64 | CGO | Chromium toolchain | +| Windows | amd64, arm64 | purego | None (library included in official releases) | +| Apple platforms | * | CGO | Xcode | +| Android | * | CGO | Android NDK | ### Windows Use `with_purego` tag. +For official releases, `libcronet.dll` is included in the archive. For self-built binaries, download from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and place in the same directory as `sing-box.exe` or in a directory listed in `PATH`. + ### Linux (purego, amd64/arm64 only) -Download `libcronet.so` from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and install to system library path or the same directory as sing-box binary, then use `with_purego` tag. +Use `with_purego` tag. + +For official releases, `libcronet.so` is included in the archive. For self-built binaries, download from [cronet-go releases](https://github.com/sagernet/cronet-go/releases) and place in the same directory as sing-box binary or in system library path. ### Linux (CGO) See [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions). +- **glibc build**: Requires glibc >= 2.31 at runtime +- **musl build**: Use `with_musl` tag, statically linked, no runtime requirements + ### Apple platforms / Android See [cronet-go](https://github.com/sagernet/cronet-go). diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index ecb09fce..70434f03 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -72,26 +72,34 @@ NaiveProxy 出站需要根据目标平台进行特殊的构建配置。 ### 支持的平台 -| 平台 | 架构 | 模式 | 要求 | -|---------------|----------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | * | purego | 无 | -| Linux | amd64, arm64 | purego | 从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载 libcronet 到系统库目录或 sing-box 二进制文件相同目录 | -| Linux | 386, amd64, arm, arm64 | CGO | Chromium 工具链(参阅 [cronet-go](https://github.com/sagernet/cronet-go)) | -| Apple 平台 | * | CGO | Xcode | -| Android | * | CGO | Android NDK | +| 平台 | 架构 | 模式 | 要求 | +|---------------|------------------------|--------|--------------------------------| +| Linux | amd64, arm64 | purego | 无(官方发布版本已包含库文件) | +| Linux | 386, amd64, arm, arm64 | CGO | Chromium 工具链,运行时需要 glibc >= 2.31 | +| Linux (musl) | 386, amd64, arm, arm64 | CGO | Chromium 工具链 | +| Windows | amd64, arm64 | purego | 无(官方发布版本已包含库文件) | +| Apple 平台 | * | CGO | Xcode | +| Android | * | CGO | Android NDK | ### Windows 使用 `with_purego` 标记。 +官方发布版本已包含 `libcronet.dll`。自行构建时,从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载并放置在 `sing-box.exe` 相同目录或 `PATH` 中的任意目录。 + ### Linux (purego, 仅 amd64/arm64) -从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载 `libcronet.so` 并安装到系统库目录或 sing-box 二进制文件相同目录,然后使用 `with_purego` 标记。 +使用 `with_purego` 标记。 + +官方发布版本已包含 `libcronet.so`。自行构建时,从 [cronet-go releases](https://github.com/sagernet/cronet-go/releases) 下载并放置在 sing-box 二进制文件相同目录或系统库路径中。 ### Linux (CGO) 参阅 [cronet-go](https://github.com/sagernet/cronet-go#linux-build-instructions)。 +- **glibc 构建**:运行时需要 glibc >= 2.31 +- **musl 构建**:使用 `with_musl` 标记,静态链接,无运行时要求 + ### Apple 平台 / Android 参阅 [cronet-go](https://github.com/sagernet/cronet-go)。 diff --git a/go.mod b/go.mod index 61d8deef..3ba45e17 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7 - github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7 + github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f + github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -108,29 +108,29 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index 789fa8d8..715ca38c 100644 --- a/go.sum +++ b/go.sum @@ -153,56 +153,56 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7 h1:jb/nr5YECJ56gcAphQ7tBWierrBbaLT7v1MI9n3e/Gw= -github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= -github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7 h1:6PjoWjKnYrz/HmEezV1Z5K39EC8l+sek1V14aXlslyc= -github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:SrXj1iQMVqZcy8XINBJOhlBncfCe7DimX6mTRY+rdDw= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b h1:+Dk1yBvaKl49l8j3YFoEvraAdt7VMy7n2Qzrs40/ekI= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b h1:tjkKLyRhD1ePdl48SjW38o7yjW1fCJ2x2nyvq5e/8oE= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b h1:P++HSm1JhmkKbDskFNfQuR8aCTg5uEWe2/5qFfj+6YU= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b h1:Rbo1r5Mk8yWlZTC8gcyuQFv2BXUI1/wWMC9Vc+cJNQ8= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b h1:VA1M5Yw09HiBD+Zemq6mOBVwBd4pr47LMN9WKOVf62Q= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AdWIsXfKxH3/hGjiYqcUSc0fb+R4vONjfRaO0emwdNA= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:sg8SupaVsj0Krc4DKSC1n2quig08bRtmsF0/iwwXeAI= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b h1:0dmsm/vEAYxQjtH4sS/A8X6bf6YqS0I0Vc6oDZdnlRc= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:jnT/bYjzvdfGVgPEgZX0Mi0qkm8qcU/DluV+TqShVPg= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b h1:/NqFcrdXS3e3Ad+ILfrwXFw3urwwFsQ1XxrDW9PkU4E= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b h1:vqeLRyeHq++RCcuUriJflTQne7hldEVJ19Or0xwCIrs= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b h1:Xr7dFoKy0o2YdPl2JcU7GtM4NxQyS8vGovd6Aw4pX8I= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b h1:GEt+x1qXt8xicDSD4GXOHs0WrVec5HAo+HmBAXzkidg= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b h1:MbjH6TmLoXlAkBWoUzuNF2w0FPfOMY6Rj9T226fe858= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AP85VNYiACL8QQeXqCUB8hz5hFOUtgwReLELRhve/4c= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b h1:4uNGGiOrJsa2S+PteucoO/Qyzz7FWHNJw2ezOkS7QiM= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b h1:N5yoxOlynwvTgaJnEOsL3iuI6FFmDJy1toyNSU+vlLA= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:JKyBNyt/DWAutvuDFjFTi0dMe0bh5zG7UUpZHH8Uqzo= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b h1:m0sCMM6ry0+eXBuTPLGY9JYOVcIvtHcDEcstMo+oSTU= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:UURnlFD48/9wn7cdi1NqYQuTvJZEFuQShxX8pvk2Fsg= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b h1:jhwpI5IXK5RPvbk9+xUV9GAw2QeRZvcZprp4bJOP9e0= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b h1:qoleSwhzgH6jDSwqktbJCPDex4yMWtijcouGR8+BL+s= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b h1:v7eakED1u8ZTKjmqxa+Eu0S5ewK+r+mfEf9KI6ymu+I= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f h1:VMlH3aVnoN0bDUUCXHRJ6o8pb3ZJe/XpRLZsUGljrJ0= +github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= +github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f h1:eZcIuGEbGT9Ldh4QP14UxVzWOeMpwltU8lWCthefaGw= +github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:qt0I3+OORnNmYDKKg3sN2/JguaNNl3ckgdoYEMeMGSA= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 h1:JPrpsOAMZL7WpRfRymdazS/LTguAJVTqTPgIM1hRGSk= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 h1:yKOa/aPDWyB8+Vw2bze3lczwA0T+Jp014F82bb04RZg= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 h1:bFWOKRv1gadjBtcCj5eSMVITdM/nFAM9WRvHGrFkW2M= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 h1:rwk5kw6tggldlnYkufttjrLgNZ4m2oDj8VPVbUkMIj8= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Kz7898MkdKck/F3KhpQ3iSbDvuMcBKgcIT9kblbEAag= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 h1:Csm66Pf4aFnrKW9SQ7/QAD+ODxrXM1y/1tOsMeWJt2Q= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:xYt0vGrcBe5E/4S1AhZrXZKqzfyMVv2EJzms+hkyMwE= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 h1:HUIwo+G+gp5xJO7oFLXE8wUBuAMSlZCff7x8BkGwrV8= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:aM0ZlZyaMsj+ckqffWuwQ/LvlEocp4+8jf0CS3Y7C08= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 h1:vAFYo+d3JZauxyW2fL8Kqqzjx2CoMSNYwS3mj2O3vig= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 h1:vitvkHM3isRtAt6J/hJbi1YEkkmoJQYxCWFyWBzetK0= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Y5OW7wtkGkAICo1c4TjsSVsRe0YY0rGXBZS3W4kWEMM= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 h1:U4fj5bK6Bp/Fc3OaQT2+pXioRTC4NkWXD8M+kcJIXx0= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 h1:6aMDLdA+GF0J6CPBAXh6yn8wCJHhmREIU8I/uCVl16w= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 h1:OvZQkFRfs5fhN1ZNEjBRj1foZfwP9EdYhLFzrF23TO4= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 h1:HJYoVGlafnfo7qwsNgocIwqg/ScCWH3MwC7oqD4Hj3I= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 h1:U/eE0HxhS6iJxBhvt2mmkxvAuwjGQ6SfGz0IZc0NWQE= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:V8mKJHu2iMoHjXH2deNQAY+Itcn9mlTeLDqhrFXkwcY= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 h1:uHy+Un5hEvONSH5+nWHLWisvfhPIAE0EWq8cLjJpAuU= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:IXNOyK596srcY+OtnrCrUdclKVL5lakKcO30H85Kryc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 h1:aPMCg3h46G8Mx5WIiYyPTr/1EQ8j4wgCI/HUPGm/occ= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 h1:snfESeRYtwkOlfQyoxfHKVNj4N0cpIpxMfsM+y52rw4= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 h1:weCViGxMO6iajrI1EjHH4hMT/cXwGc+HhXH24xUrVGE= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= diff --git a/test/go.mod b/test/go.mod index 1695a452..3e934f9e 100644 --- a/test/go.mod +++ b/test/go.mod @@ -86,6 +86,7 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/openai/openai-go/v3 v3.13.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect @@ -97,32 +98,37 @@ require ( github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 // indirect - github.com/sagernet/cronet-go/all v0.0.0-20251212022647-84c3c9e2a88e // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251212022311-629f90088dc7 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251212022311-629f90088dc7 // indirect + github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/sing-mux v0.3.3 // indirect github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect - github.com/sagernet/sing-tun v0.8.0-beta.11 // indirect + github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e // indirect github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect github.com/sagernet/smux v1.5.34-mod.2 // indirect github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 // indirect diff --git a/test/go.sum b/test/go.sum index 7d3ee098..ebba7fe1 100644 --- a/test/go.sum +++ b/test/go.sum @@ -156,6 +156,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q= +github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= 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= @@ -181,44 +183,54 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 h1:ql2eCQp1sIinoSwNcJW+tBGToRoxm0rsU8uqRJA9Vao= github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= -github.com/sagernet/cronet-go/all v0.0.0-20251212022647-84c3c9e2a88e h1:8EbRGPgjPVd54A42d8kPHkBemndix3E9X4ynKvgrKPI= -github.com/sagernet/cronet-go/all v0.0.0-20251212022647-84c3c9e2a88e/go.mod h1:LXgFATxmlI7hhzwYYsiu1IhhRFCMykcE/xhehiIeUMo= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251212022311-629f90088dc7 h1:eRDi6flT6kKRXKQanFJqyEBgvGlGjJ3cM1rC0ClaL9o= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251212022311-629f90088dc7/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251212022311-629f90088dc7 h1:t8YuHZccd0VLN4sdzBsum3k1UHlgPfsM3VZ1QWTXz6U= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251212022311-629f90088dc7 h1:Eqg+x5hVwau/GeVoS6OfL9gtrqnNgYgA0rQx3r60UGY= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251212022311-629f90088dc7/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251212022311-629f90088dc7 h1:dXDL2R+cI8uGH2AmWzIAqvSrnybMXgZCtSH7woMdqug= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251212022311-629f90088dc7 h1:Cnm7ZmvIU0lKCw9j1cOj95wOottI20UeFUKhCRwWoYM= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251212022311-629f90088dc7 h1:Oq4kAHfgSSR0y1+4WtrhZ9uHPBOns3JKlAaIpTp1JEc= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251212022311-629f90088dc7 h1:m4pXckZD0tAwdVzDW2VDEpa0VmU1xK9zkoDpGmW/DeA= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251212022311-629f90088dc7 h1:GHPHZwYvdA+nuyBy3BbFqdoZBNIaerbpu9cpSWddYAw= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251212022311-629f90088dc7/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251212022311-629f90088dc7 h1:r4dbGMdvZTPbaqU0yHHgMuB589tAcK8smJNFkZp7HLI= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251212022311-629f90088dc7 h1:Wn6FoJyJMfQhz8HAKjdCiDNzcwLIsWaqOZnhodY/Me8= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251212022311-629f90088dc7 h1:Dl/IYvylbtHw3AZx9RQyqCZGTS12e/NGDSZuKXlXWHw= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251212022311-629f90088dc7 h1:/vwk591fhV7laT03+uxaqnvueEpF9m1dyNO7r/aGFGA= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251212022311-629f90088dc7/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251212022311-629f90088dc7 h1:deoqtkWRhtDdnYlMEKvbCByzTVlK6FF/oVx94Nee5gc= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251212022311-629f90088dc7 h1:jc4TolAOHA2GRtUaGF2BiJyW1bwYjWh4ysHo+fTarwc= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251212022311-629f90088dc7 h1:+GThq5QNch7aZDOylMnTzCoxYLVC33e3hTP/yqp3BV4= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251212022311-629f90088dc7/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251212022311-629f90088dc7 h1:TrjtzYFKw3ibgO5KFM4Q6K24IXx1U4+fOlV1sklnZ9I= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251212022311-629f90088dc7/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251212022311-629f90088dc7 h1:zwGAXQo4rvonaUAwd+TUlpeYMcsCW1lE6JomxAxLTmc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251212022311-629f90088dc7 h1:ohNspkx6sP6iKJyFvuecreQFQPpbC61kFoZTquSJ7q4= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251212022311-629f90088dc7/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f h1:eZcIuGEbGT9Ldh4QP14UxVzWOeMpwltU8lWCthefaGw= +github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:qt0I3+OORnNmYDKKg3sN2/JguaNNl3ckgdoYEMeMGSA= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 h1:JPrpsOAMZL7WpRfRymdazS/LTguAJVTqTPgIM1hRGSk= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 h1:yKOa/aPDWyB8+Vw2bze3lczwA0T+Jp014F82bb04RZg= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 h1:bFWOKRv1gadjBtcCj5eSMVITdM/nFAM9WRvHGrFkW2M= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 h1:rwk5kw6tggldlnYkufttjrLgNZ4m2oDj8VPVbUkMIj8= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Kz7898MkdKck/F3KhpQ3iSbDvuMcBKgcIT9kblbEAag= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 h1:Csm66Pf4aFnrKW9SQ7/QAD+ODxrXM1y/1tOsMeWJt2Q= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:xYt0vGrcBe5E/4S1AhZrXZKqzfyMVv2EJzms+hkyMwE= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 h1:HUIwo+G+gp5xJO7oFLXE8wUBuAMSlZCff7x8BkGwrV8= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:aM0ZlZyaMsj+ckqffWuwQ/LvlEocp4+8jf0CS3Y7C08= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 h1:vAFYo+d3JZauxyW2fL8Kqqzjx2CoMSNYwS3mj2O3vig= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 h1:vitvkHM3isRtAt6J/hJbi1YEkkmoJQYxCWFyWBzetK0= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Y5OW7wtkGkAICo1c4TjsSVsRe0YY0rGXBZS3W4kWEMM= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 h1:U4fj5bK6Bp/Fc3OaQT2+pXioRTC4NkWXD8M+kcJIXx0= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 h1:6aMDLdA+GF0J6CPBAXh6yn8wCJHhmREIU8I/uCVl16w= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 h1:OvZQkFRfs5fhN1ZNEjBRj1foZfwP9EdYhLFzrF23TO4= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 h1:HJYoVGlafnfo7qwsNgocIwqg/ScCWH3MwC7oqD4Hj3I= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 h1:U/eE0HxhS6iJxBhvt2mmkxvAuwjGQ6SfGz0IZc0NWQE= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:V8mKJHu2iMoHjXH2deNQAY+Itcn9mlTeLDqhrFXkwcY= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 h1:uHy+Un5hEvONSH5+nWHLWisvfhPIAE0EWq8cLjJpAuU= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:IXNOyK596srcY+OtnrCrUdclKVL5lakKcO30H85Kryc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 h1:aPMCg3h46G8Mx5WIiYyPTr/1EQ8j4wgCI/HUPGm/occ= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 h1:snfESeRYtwkOlfQyoxfHKVNj4N0cpIpxMfsM+y52rw4= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 h1:weCViGxMO6iajrI1EjHH4hMT/cXwGc+HhXH24xUrVGE= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= @@ -242,8 +254,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.11 h1:xVi8VcVkvz2o+3v1PLv5MOkFpiVCwjLjucVlmigDi5c= -github.com/sagernet/sing-tun v0.8.0-beta.11/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e h1:ZEv+9vy7vC1vbr3LfwZGx3JAOkl/w4+hnGamHw4W36M= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= From 0585f6d06579ac546604f015ba4dfa8394816397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 17 Dec 2025 21:45:18 +0800 Subject: [PATCH 055/185] Add ECH support for NaiveProxy outbound and tls.ech.query_server_name option - Enable ECH for NaiveProxy outbound with DNS resolver integration - Add query_server_name option to override domain for ECH HTTPS record queries - Update cronet-go dependency and remove windows_386 support --- .github/CRONET_GO_VERSION | 2 +- common/tls/ech.go | 23 ++++-- docs/configuration/outbound/naive.md | 2 +- docs/configuration/outbound/naive.zh.md | 2 +- docs/configuration/shared/tls.md | 12 +++ docs/configuration/shared/tls.zh.md | 12 +++ go.mod | 49 ++++++------- go.sum | 98 ++++++++++++------------- option/tls.go | 7 +- protocol/naive/outbound.go | 50 ++++++++++++- 10 files changed, 167 insertions(+), 90 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index ad1bced9..399c1b4a 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -78951bf5e641fcb42bc82b73e70bd28d819e5e55 +4f12714a37cc546cbd171765e44988a348ba77fa diff --git a/common/tls/ech.go b/common/tls/ech.go index 5e9fba6d..37573bf1 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -51,6 +51,7 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op return &ECHClientConfig{ ECHCapableConfig: clientConfig, dnsRouter: service.FromContext[adapter.DNSRouter](ctx), + queryServerName: options.ECH.QueryServerName, }, nil } } @@ -108,10 +109,11 @@ func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) { type ECHClientConfig struct { ECHCapableConfig - access sync.Mutex - dnsRouter adapter.DNSRouter - lastTTL time.Duration - lastUpdate time.Time + access sync.Mutex + dnsRouter adapter.DNSRouter + queryServerName string + lastTTL time.Duration + lastUpdate time.Time } func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) { @@ -130,13 +132,17 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) s.access.Lock() defer s.access.Unlock() if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL { + queryServerName := s.queryServerName + if queryServerName == "" { + queryServerName = s.ServerName() + } message := &mDNS.Msg{ MsgHdr: mDNS.MsgHdr{ RecursionDesired: true, }, Question: []mDNS.Question{ { - Name: mDNS.Fqdn(s.ServerName()), + Name: mDNS.Fqdn(queryServerName), Qtype: mDNS.TypeHTTPS, Qclass: mDNS.ClassINET, }, @@ -175,7 +181,12 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) } func (s *ECHClientConfig) Clone() Config { - return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate} + return &ECHClientConfig{ + ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), + dnsRouter: s.dnsRouter, + queryServerName: s.queryServerName, + lastUpdate: s.lastUpdate, + } } func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) { diff --git a/docs/configuration/outbound/naive.md b/docs/configuration/outbound/naive.md index f1042492..f442a8d4 100644 --- a/docs/configuration/outbound/naive.md +++ b/docs/configuration/outbound/naive.md @@ -86,7 +86,7 @@ See [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details. TLS configuration, see [TLS](/configuration/shared/tls/#outbound). -Only `server_name`, `certificate`, `certificate_path` and `certificate_public_key_sha256` are supported. +Only `server_name`, `certificate`, `certificate_path`, `certificate_public_key_sha256` and `ech` are supported. ### Dial Fields diff --git a/docs/configuration/outbound/naive.zh.md b/docs/configuration/outbound/naive.zh.md index 26067473..ec719e24 100644 --- a/docs/configuration/outbound/naive.zh.md +++ b/docs/configuration/outbound/naive.zh.md @@ -86,7 +86,7 @@ UDP over TCP 配置。 TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 -只有 `server_name`、`certificate`、`certificate_path` 和 `certificate_public_key_sha256` 是被支持的。 +只有 `server_name`、`certificate`、`certificate_path`、`certificate_public_key_sha256` 和 `ech` 是被支持的。 ### 拨号字段 diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index b1ec1e66..1350912f 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -14,6 +14,7 @@ icon: material/new-box :material-plus: [client_key_path](#client_key_path) :material-plus: [client_authentication](#client_authentication) :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) + :material-plus: [ech.query_server_name](#query_server_name) !!! quote "Changes in sing-box 1.12.0" @@ -118,6 +119,7 @@ icon: material/new-box "enabled": false, "config": [], "config_path": "", + "query_server_name": "", // Deprecated "pq_signature_schemes_enabled": false, @@ -514,6 +516,16 @@ The path to ECH configuration, in PEM format. If empty, load from DNS will be attempted. +#### query_server_name + +!!! question "Since sing-box 1.13.0" + +==Client only== + +Overrides the domain name used for ECH HTTPS record queries. + +If empty, `server_name` is used for queries. + #### fragment !!! question "Since sing-box 1.12.0" diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 1cc07b32..2d507f8a 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -14,6 +14,7 @@ icon: material/new-box :material-plus: [client_key_path](#client_key_path) :material-plus: [client_authentication](#client_authentication) :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) + :material-plus: [ech.query_server_name](#query_server_name) !!! quote "sing-box 1.12.0 中的更改" @@ -118,6 +119,7 @@ icon: material/new-box "enabled": false, "config": [], "config_path": "", + "query_server_name": "", // 废弃的 "pq_signature_schemes_enabled": false, @@ -510,6 +512,16 @@ ECH 配置路径,PEM 格式。 如果为空,将尝试从 DNS 加载。 +#### query_server_name + +!!! question "自 sing-box 1.13.0 起" + +==仅客户端== + +覆盖用于 ECH HTTPS 记录查询的域名。 + +如果为空,使用 `server_name` 查询。 + #### fragment !!! question "自 sing-box 1.12.0 起" diff --git a/go.mod b/go.mod index 3ba45e17..7f7b829d 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f - github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f + github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585 + github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -108,29 +108,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index 715ca38c..a13c708a 100644 --- a/go.sum +++ b/go.sum @@ -153,56 +153,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f h1:VMlH3aVnoN0bDUUCXHRJ6o8pb3ZJe/XpRLZsUGljrJ0= -github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= -github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f h1:eZcIuGEbGT9Ldh4QP14UxVzWOeMpwltU8lWCthefaGw= -github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:qt0I3+OORnNmYDKKg3sN2/JguaNNl3ckgdoYEMeMGSA= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 h1:JPrpsOAMZL7WpRfRymdazS/LTguAJVTqTPgIM1hRGSk= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 h1:yKOa/aPDWyB8+Vw2bze3lczwA0T+Jp014F82bb04RZg= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 h1:bFWOKRv1gadjBtcCj5eSMVITdM/nFAM9WRvHGrFkW2M= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 h1:rwk5kw6tggldlnYkufttjrLgNZ4m2oDj8VPVbUkMIj8= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Kz7898MkdKck/F3KhpQ3iSbDvuMcBKgcIT9kblbEAag= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 h1:Csm66Pf4aFnrKW9SQ7/QAD+ODxrXM1y/1tOsMeWJt2Q= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:xYt0vGrcBe5E/4S1AhZrXZKqzfyMVv2EJzms+hkyMwE= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 h1:HUIwo+G+gp5xJO7oFLXE8wUBuAMSlZCff7x8BkGwrV8= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:aM0ZlZyaMsj+ckqffWuwQ/LvlEocp4+8jf0CS3Y7C08= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 h1:vAFYo+d3JZauxyW2fL8Kqqzjx2CoMSNYwS3mj2O3vig= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 h1:vitvkHM3isRtAt6J/hJbi1YEkkmoJQYxCWFyWBzetK0= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Y5OW7wtkGkAICo1c4TjsSVsRe0YY0rGXBZS3W4kWEMM= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 h1:U4fj5bK6Bp/Fc3OaQT2+pXioRTC4NkWXD8M+kcJIXx0= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 h1:6aMDLdA+GF0J6CPBAXh6yn8wCJHhmREIU8I/uCVl16w= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 h1:OvZQkFRfs5fhN1ZNEjBRj1foZfwP9EdYhLFzrF23TO4= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 h1:HJYoVGlafnfo7qwsNgocIwqg/ScCWH3MwC7oqD4Hj3I= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 h1:U/eE0HxhS6iJxBhvt2mmkxvAuwjGQ6SfGz0IZc0NWQE= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:V8mKJHu2iMoHjXH2deNQAY+Itcn9mlTeLDqhrFXkwcY= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 h1:uHy+Un5hEvONSH5+nWHLWisvfhPIAE0EWq8cLjJpAuU= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:IXNOyK596srcY+OtnrCrUdclKVL5lakKcO30H85Kryc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 h1:aPMCg3h46G8Mx5WIiYyPTr/1EQ8j4wgCI/HUPGm/occ= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 h1:snfESeRYtwkOlfQyoxfHKVNj4N0cpIpxMfsM+y52rw4= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 h1:weCViGxMO6iajrI1EjHH4hMT/cXwGc+HhXH24xUrVGE= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585 h1:fu/J7Z+umFy5JwIxo2/7ixvuMRouJx93VU0RUXQx8aI= +github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585 h1:QN3WvGKiBg9N5vrSVKJzNTmK/BfEVMJNbRZDOlOfUpo= +github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585/go.mod h1:JTqmHTkUqWDX5Hz+jnx8kdDKJ07U80QS1hN0Wfe7CTI= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f h1:hjyZ7QZWWxYEW13lBT3+yAk7yENgKBuqhiw5rl/oxng= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:RHPD2Opa4kGqlTtHyhtMWhzvr3F1PZsT6GW+IMBaMYs= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f h1:wPrD+ADgFZC7AFFqUDWaKdcElqGODX10WrNpmUywX00= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:MC0T2f2SXwai6hdmTkkulE7PlHA11lHp1re7alZCQLo= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:X3RxBK5D5P3fcb+j8HqiEwhXEGMZkCg9ecmGYHo2qpg= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:K/CJaXL1djRbHyoVHGNx9nacncZPKE0QkEoO12nWL4s= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:uN/ay0BtmWWPdaoAsK5yu+bbQfr6iujEUTo7zZhF3a4= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:vdDIjy2y8DQEu1tVkpS/WOx0fEbK4yjMQv7X1e7MbFU= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:BElLblhAfW8Yd5ehlI9xk1FhlsGnKIX6oVak3C8cCnc= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f h1:vALFH1gx8nLLiAqkTy7tgrT9uLWCPC4e+Ir1UvEQkRw= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f h1:vQ99pp3CGklVoUGO2x//xne3BkfzSmNKDOzB5V9BoaU= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:MqINoyziFNrN8t5d/XCu1z+BZs2CkRLLKKOdx0g6bJ4= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f h1:L7HQmTsLFI7nAzyi2RIkmkuraSY7LRivBYoflIjQcxk= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f h1:DAswotlvnP6zYlP2KleGfMUy4dIBNNYGg3sh1reS53I= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:b2OhFosX8oUEdOpYkDS4fkbotbEe9WRMCpYGAWWq4VI= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f h1:H+GUhGCYe/4wHi+XRw96AgFxETSE3yCDhyAH7G6GbQc= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f h1:ziXSJVeboo4ZiCulyMbbEgG9ArfP98hqN1pALA15XeM= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:X9FaZ28M35Hj23agNDRDCklSrR0zb8PZz4K4EBalILE= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:qKlncyWLNJ3YtJrF3hK5YPd2Tb66ZF1iQXjq1mEADhE= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:qpBg3PtrVx4Tgv+AwwoRgPqYsfEZeZ3Jn+zHfqr7Z1A= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:PvjxP4e6qosQrhkPrpW2ZnRm2OKZsvEXIADV29fKWeY= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:l3+QN68ENC0KHCxHa0iUO0vMGiG00TJlHgH8+jVrDNU= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= diff --git a/option/tls.go b/option/tls.go index 1829898a..60343a15 100644 --- a/option/tls.go +++ b/option/tls.go @@ -215,9 +215,10 @@ type InboundECHOptions struct { } type OutboundECHOptions struct { - Enabled bool `json:"enabled,omitempty"` - Config badoption.Listable[string] `json:"config,omitempty"` - ConfigPath string `json:"config_path,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Config badoption.Listable[string] `json:"config,omitempty"` + ConfigPath string `json:"config_path,omitempty"` + QueryServerName string `json:"query_server_name,omitempty"` // Deprecated: not supported by stdlib PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index 963709c0..bcb77c30 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -4,6 +4,7 @@ package naive import ( "context" + "encoding/pem" "net" "os" "strings" @@ -14,6 +15,7 @@ import ( "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" @@ -22,6 +24,9 @@ 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" + + mDNS "github.com/miekg/dns" ) func RegisterOutbound(registry *outbound.Registry) { @@ -73,9 +78,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if options.TLS.KernelTx || options.TLS.KernelRx { return nil, E.New("kernel TLS is not supported on naive outbound") } - if options.TLS.ECH != nil && options.TLS.ECH.Enabled { - return nil, E.New("ECH is not currently supported on naive outbound") - } if options.TLS.UTLS != nil && options.TLS.UTLS.Enabled { return nil, E.New("uTLS is not supported on naive outbound") } @@ -121,6 +123,44 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } } + dnsRouter := service.FromContext[adapter.DNSRouter](ctx) + var dnsResolver cronet.DNSResolverFunc + if dnsRouter != nil { + dnsResolver = func(dnsContext context.Context, request *mDNS.Msg) *mDNS.Msg { + response, err := dnsRouter.Exchange(dnsContext, request, adapter.DNSQueryOptions{}) + if err != nil { + logger.Error("DNS exchange failed: ", err) + return dns.FixedResponseStatus(request, mDNS.RcodeServerFailure) + } + return response + } + } + + var echEnabled bool + var echConfigList []byte + var echQueryServerName string + if options.TLS.ECH != nil && options.TLS.ECH.Enabled { + echEnabled = true + echQueryServerName = options.TLS.ECH.QueryServerName + var echConfig []byte + if len(options.TLS.ECH.Config) > 0 { + echConfig = []byte(strings.Join(options.TLS.ECH.Config, "\n")) + } else if options.TLS.ECH.ConfigPath != "" { + content, err := os.ReadFile(options.TLS.ECH.ConfigPath) + if err != nil { + return nil, E.Cause(err, "read ECH config") + } + echConfig = content + } + if len(echConfig) > 0 { + block, rest := pem.Decode(echConfig) + if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 { + return nil, E.New("invalid ECH configs pem") + } + echConfigList = block.Bytes + } + } + client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{ Context: ctx, ServerAddress: serverAddress, @@ -132,6 +172,10 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL TrustedRootCertificates: trustedRootCertificates, TrustedCertificatePublicKeySHA256: options.TLS.CertificatePublicKeySHA256, Dialer: outboundDialer, + DNSResolver: dnsResolver, + ECHEnabled: echEnabled, + ECHConfigList: echConfigList, + ECHQueryServerName: echQueryServerName, }) if err != nil { return nil, err From 48b7adde7dd954391015f3710725f836f937ec47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 18 Dec 2025 17:08:36 +0800 Subject: [PATCH 056/185] Add QUIC support for naiveproxy --- .github/CRONET_GO_VERSION | 2 +- docs/configuration/inbound/naive.md | 40 +++-- docs/configuration/inbound/naive.zh.md | 38 +++-- docs/configuration/outbound/naive.md | 19 +++ docs/configuration/outbound/naive.zh.md | 19 +++ go.mod | 52 +++---- go.sum | 104 ++++++------- include/quic_stub.go | 2 +- option/naive.go | 27 +++- protocol/naive/inbound.go | 5 +- protocol/naive/outbound.go | 19 ++- protocol/naive/quic/inbound_init.go | 72 ++++++++- test/go.mod | 53 ++++--- test/go.sum | 106 +++++++------ test/naive_self_test.go | 194 +++++++++++++++++++++++- 15 files changed, 553 insertions(+), 199 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 399c1b4a..74f690d2 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -4f12714a37cc546cbd171765e44988a348ba77fa +446096b34fb07e86541d927be26f8e5c42d41b4a diff --git a/docs/configuration/inbound/naive.md b/docs/configuration/inbound/naive.md index 0b4ff4b7..05c5b9f9 100644 --- a/docs/configuration/inbound/naive.md +++ b/docs/configuration/inbound/naive.md @@ -2,19 +2,20 @@ ```json { - "type": "naive", - "tag": "naive-in", - "network": "udp", +"type": "naive", +"tag": "naive-in", +"network": "udp", +... +// Listen Fields - ... // Listen Fields - - "users": [ - { - "username": "sekai", - "password": "password" - } - ], - "tls": {} +"users": [ +{ +"username": "sekai", +"password": "password" +} +], +"quic_congestion_control": "", +"tls": {} } ``` @@ -36,6 +37,21 @@ Both if empty. Naive users. +#### quic_congestion_control + +QUIC congestion control algorithm. + +| Algorithm | Description | +|----------------|---------------------------------| +| `bbr` | BBR | +| `bbr_standard` | BBR (Standard version) | +| `bbr2` | BBRv2 | +| `bbr2_variant` | BBRv2 (An experimental variant) | +| `cubic` | CUBIC | +| `reno` | New Reno | + +`bbr` is used by default (the default of QUICHE, used by Chromium which NaiveProxy is based on). + #### tls TLS configuration, see [TLS](/configuration/shared/tls/#inbound). \ No newline at end of file diff --git a/docs/configuration/inbound/naive.zh.md b/docs/configuration/inbound/naive.zh.md index 5707e653..bc2620b7 100644 --- a/docs/configuration/inbound/naive.zh.md +++ b/docs/configuration/inbound/naive.zh.md @@ -2,19 +2,20 @@ ```json { - "type": "naive", - "tag": "naive-in", - "network": "udp", +"type": "naive", +"tag": "naive-in", +"network": "udp", - ... // 监听字段 +... // 监听字段 - "users": [ - { - "username": "sekai", - "password": "password" - } - ], - "tls": {} +"users": [ +{ +"username": "sekai", +"password": "password" +} +], +"quic_congestion_control": "", +"tls": {} } ``` @@ -36,6 +37,21 @@ Naive 用户。 +#### quic_congestion_control + +QUIC 拥塞控制算法。 + +| 算法 | 描述 | +|----------------|--------------------| +| `bbr` | BBR | +| `bbr_standard` | BBR (标准版) | +| `bbr2` | BBRv2 | +| `bbr2_variant` | BBRv2 (一种试验变体) | +| `cubic` | CUBIC | +| `reno` | New Reno | + +默认使用 `bbr`(NaiveProxy 基于的 Chromium 使用的 QUICHE 的默认值)。 + #### tls TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 \ No newline at end of file diff --git a/docs/configuration/outbound/naive.md b/docs/configuration/outbound/naive.md index f442a8d4..a56036d9 100644 --- a/docs/configuration/outbound/naive.md +++ b/docs/configuration/outbound/naive.md @@ -18,6 +18,8 @@ icon: material/new-box "insecure_concurrency": 0, "extra_headers": {}, "udp_over_tcp": false | {}, + "quic": false, + "quic_congestion_control": "", "tls": {}, ... // Dial Fields @@ -80,6 +82,23 @@ UDP over TCP protocol settings. See [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details. +#### quic + +Use QUIC instead of HTTP/2. + +#### quic_congestion_control + +QUIC congestion control algorithm. + +| Algorithm | Description | +|-----------|-------------| +| `bbr` | BBR | +| `bbr2` | BBRv2 | +| `cubic` | CUBIC | +| `reno` | New Reno | + +`bbr` is used by default (the default of QUICHE, used by Chromium which NaiveProxy is based on). + #### tls ==Required== diff --git a/docs/configuration/outbound/naive.zh.md b/docs/configuration/outbound/naive.zh.md index ec719e24..40bcc2e5 100644 --- a/docs/configuration/outbound/naive.zh.md +++ b/docs/configuration/outbound/naive.zh.md @@ -18,6 +18,8 @@ icon: material/new-box "insecure_concurrency": 0, "extra_headers": {}, "udp_over_tcp": false | {}, + "quic": false, + "quic_congestion_control": "", "tls": {}, ... // 拨号字段 @@ -80,6 +82,23 @@ UDP over TCP 配置。 参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp/)。 +#### quic + +使用 QUIC 代替 HTTP/2。 + +#### quic_congestion_control + +QUIC 拥塞控制算法。 + +| 算法 | 描述 | +|------|------| +| `bbr` | BBR | +| `bbr2` | BBRv2 | +| `cubic` | CUBIC | +| `reno` | New Reno | + +默认使用 `bbr`(NaiveProxy 基于的 Chromium 使用的 QUICHE 的默认值)。 + #### tls ==必填== diff --git a/go.mod b/go.mod index 7f7b829d..994922f2 100644 --- a/go.mod +++ b/go.mod @@ -26,15 +26,15 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585 - github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585 + github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f + github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 - github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 + github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.5 + github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 @@ -108,28 +108,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index a13c708a..c2ceaafc 100644 --- a/go.sum +++ b/go.sum @@ -153,54 +153,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585 h1:fu/J7Z+umFy5JwIxo2/7ixvuMRouJx93VU0RUXQx8aI= -github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585 h1:QN3WvGKiBg9N5vrSVKJzNTmK/BfEVMJNbRZDOlOfUpo= -github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585/go.mod h1:JTqmHTkUqWDX5Hz+jnx8kdDKJ07U80QS1hN0Wfe7CTI= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f h1:hjyZ7QZWWxYEW13lBT3+yAk7yENgKBuqhiw5rl/oxng= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:RHPD2Opa4kGqlTtHyhtMWhzvr3F1PZsT6GW+IMBaMYs= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f h1:wPrD+ADgFZC7AFFqUDWaKdcElqGODX10WrNpmUywX00= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:MC0T2f2SXwai6hdmTkkulE7PlHA11lHp1re7alZCQLo= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:X3RxBK5D5P3fcb+j8HqiEwhXEGMZkCg9ecmGYHo2qpg= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:K/CJaXL1djRbHyoVHGNx9nacncZPKE0QkEoO12nWL4s= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:uN/ay0BtmWWPdaoAsK5yu+bbQfr6iujEUTo7zZhF3a4= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:vdDIjy2y8DQEu1tVkpS/WOx0fEbK4yjMQv7X1e7MbFU= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:BElLblhAfW8Yd5ehlI9xk1FhlsGnKIX6oVak3C8cCnc= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f h1:vALFH1gx8nLLiAqkTy7tgrT9uLWCPC4e+Ir1UvEQkRw= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f h1:vQ99pp3CGklVoUGO2x//xne3BkfzSmNKDOzB5V9BoaU= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:MqINoyziFNrN8t5d/XCu1z+BZs2CkRLLKKOdx0g6bJ4= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f h1:L7HQmTsLFI7nAzyi2RIkmkuraSY7LRivBYoflIjQcxk= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f h1:DAswotlvnP6zYlP2KleGfMUy4dIBNNYGg3sh1reS53I= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:b2OhFosX8oUEdOpYkDS4fkbotbEe9WRMCpYGAWWq4VI= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f h1:H+GUhGCYe/4wHi+XRw96AgFxETSE3yCDhyAH7G6GbQc= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f h1:ziXSJVeboo4ZiCulyMbbEgG9ArfP98hqN1pALA15XeM= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:X9FaZ28M35Hj23agNDRDCklSrR0zb8PZz4K4EBalILE= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:qKlncyWLNJ3YtJrF3hK5YPd2Tb66ZF1iQXjq1mEADhE= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:qpBg3PtrVx4Tgv+AwwoRgPqYsfEZeZ3Jn+zHfqr7Z1A= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:PvjxP4e6qosQrhkPrpW2ZnRm2OKZsvEXIADV29fKWeY= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:l3+QN68ENC0KHCxHa0iUO0vMGiG00TJlHgH8+jVrDNU= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f h1:WTHyVtd9nNZ4VB20aja31e0ZXXGrVlssAanJJBMc5BU= +github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f h1:qupezSQEMraq2yajI4HrWf9h9rY7RESUYbYHRRd49FY= +github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:onaFo5hJh1KsvuxkYTFLokh0Yx2oBh9J3yvFTnFB1Fc= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:jmDdDxVFN/W+pX/QRHYR6jFu5gosRPNSSPemGmchHpM= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:zQeDQJg2YZSlmR5+mYV4KyqIWwe+SH5hnxTO4EalDwA= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:85ck9+7Ftj+J3RO1uusn1Y3EXRLX9Dy0WIK/ZjRlQok= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:D//nc8RxHp7tTKcIYYQZ80wQBwxlCJv6HpkrCkqIVRo= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:wwrQCcrwnHSY/Y4cy8JCiZLzuWXM8tENphhJtFplh0c= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:Q52ojQVXk9f6Z+EWHWv+SJajNF56bVvgMyQP/7WV2TI= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:UzXQ09sZQZDlsZvWd4SAhtaF0RMxAVg4TEYsRNMutjU= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:sjSknfRi3fMJqwHW4pNL64SGq7Y5XIm4pz5Ds21hvKw= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:uxb8pqzB4KH7ai6MocOWQPu3/+BAYISSIMPHWs0wtrQ= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:uV78fIQ2XdVQUzowiafKP2fs+rGlkI+9u7l1cBMlQlc= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:6d87Pvio5dpWakic3SNBVTwTlt8ZnvL1GWPPeY2xSLs= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:tc3GdN9pAfZqdfb55DmtUSXEuKuecWNYSD/+4jclXmo= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:W5fN1B1wYgnh7Y5Nw+MgcDjsvdPvEPAGe27iemq2LJs= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:xfNH8CO8p2W8rtVFgyv8kiAkzoSArW6+h2s/p5ZNvP8= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:1mkhgxLOh1pPjBLgC/GK3Rhhinz0254OjGJRrivVhQ8= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:zf252WwVhJYduTDBXvkBKdwSU+Ce0OToQzvy5SwPS3c= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:Nhkn2M2UsGe0hr6qOgNKvGKgAbo6EfWQto+YjlNWGqg= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:HSvWFyBDLC7grFuSwBSIaxLI/MVpe/w4qJJAWAZsBxc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:PJa1fhmbXWXCYk07DZhqzjP2u9rM/cnS0A1aaPU56pY= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:Tpskq8p8boYHkBya18ythhgWTMv8gFsseGk55MFW+/k= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:qjB64lzTP0ROuk9GCCwsNQ9ca37nIr75iOyc2vuGFQg= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:GDJjygR/HLWOzwAs0RjhvqN6d1rUm7WiDAmHNe8gRAM= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= @@ -211,14 +211,14 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 h1:Rah/tDukrowqlznHQgXD4E9/yEsVsEMIxBZzS2NorGc= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.3/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4= -github.com/sagernet/sing-quic v0.6.0-beta.5/go.mod h1:9D9GANrK33NjWCe1VkU5L5+8MxU39WrduBSmHuHz8GA= +github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 h1:YNXy008FFkVwfNjCR8GlVTlHbBXPwv8Y4ytDnSFUSDo= +github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0/go.mod h1:Y3YVjPutLHLQvYjGtUFH+w8YmM4MTd19NDzxLZGNGIs= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= diff --git a/include/quic_stub.go b/include/quic_stub.go index c20a5114..d2c03b98 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -44,7 +44,7 @@ func registerQUICInbounds(registry *inbound.Registry) { inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { return nil, C.ErrQUICNotIncluded }) - naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) { + naive.ConfigureHTTP3ListenerFunc = func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) { return nil, C.ErrQUICNotIncluded } } diff --git a/option/naive.go b/option/naive.go index d72390be..fcc315b6 100644 --- a/option/naive.go +++ b/option/naive.go @@ -5,20 +5,33 @@ import ( "github.com/sagernet/sing/common/json/badoption" ) +type QuicheCongestionControl string + +const ( + QuicheCongestionControlDefault QuicheCongestionControl = "" + QuicheCongestionControlBBR QuicheCongestionControl = "TBBR" + QuicheCongestionControlBBRv2 QuicheCongestionControl = "B2ON" + QuicheCongestionControlCubic QuicheCongestionControl = "QBIC" + QuicheCongestionControlReno QuicheCongestionControl = "RENO" +) + type NaiveInboundOptions struct { ListenOptions - Users []auth.User `json:"users,omitempty"` - Network NetworkList `json:"network,omitempty"` + Users []auth.User `json:"users,omitempty"` + Network NetworkList `json:"network,omitempty"` + QUICCongestionControl string `json:"quic_congestion_control,omitempty"` InboundTLSOptionsContainer } type NaiveOutboundOptions struct { DialerOptions ServerOptions - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - InsecureConcurrency int `json:"insecure_concurrency,omitempty"` - ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"` - UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + InsecureConcurrency int `json:"insecure_concurrency,omitempty"` + ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"` + UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` + QUIC bool `json:"quic,omitempty"` + QUICCongestionControl string `json:"quic_congestion_control,omitempty"` OutboundTLSOptionsContainer } diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 6354f011..83938594 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -29,7 +29,7 @@ import ( "golang.org/x/net/http2/h2c" ) -var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) +var ConfigureHTTP3ListenerFunc func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) func RegisterInbound(registry *inbound.Registry) { inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound) @@ -40,6 +40,7 @@ type Inbound struct { ctx context.Context router adapter.ConnectionRouterEx logger logger.ContextLogger + options option.NaiveInboundOptions listener *listener.Listener network []string networkIsDefault bool @@ -121,7 +122,7 @@ func (n *Inbound) Start(stage adapter.StartStage) error { } if common.Contains(n.network, N.NetworkUDP) { - http3Server, err := ConfigureHTTP3ListenerFunc(n.listener, n, n.tlsConfig, n.logger) + http3Server, err := ConfigureHTTP3ListenerFunc(n.ctx, n.logger, n.listener, n, n.tlsConfig, n.options) if err == nil { n.h3Server = http3Server } else if len(n.network) > 1 { diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index bcb77c30..ebaf373d 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -160,7 +160,21 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL echConfigList = block.Bytes } } - + var quicCongestionControl cronet.QUICCongestionControl + switch options.QUICCongestionControl { + case "": + quicCongestionControl = cronet.QUICCongestionControlDefault + case "bbr": + quicCongestionControl = cronet.QUICCongestionControlBBR + case "bbr2": + quicCongestionControl = cronet.QUICCongestionControlBBRv2 + case "cubic": + quicCongestionControl = cronet.QUICCongestionControlCubic + case "reno": + quicCongestionControl = cronet.QUICCongestionControlReno + default: + return nil, E.New("unknown quic congestion control: ", options.QUICCongestionControl) + } client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{ Context: ctx, ServerAddress: serverAddress, @@ -176,11 +190,12 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL ECHEnabled: echEnabled, ECHConfigList: echConfigList, ECHQueryServerName: echQueryServerName, + QUIC: options.QUIC, + QUICCongestionControl: quicCongestionControl, }) if err != nil { return nil, err } - var uotClient *uot.Client uotOptions := common.PtrValueOrDefault(options.UDPOverTCP) if uotOptions.Enabled { diff --git a/protocol/naive/quic/inbound_init.go b/protocol/naive/quic/inbound_init.go index f495c860..956132ac 100644 --- a/protocol/naive/quic/inbound_init.go +++ b/protocol/naive/quic/inbound_init.go @@ -1,21 +1,29 @@ package quic import ( + "context" "io" "net/http" "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/congestion" "github.com/sagernet/quic-go/http3" "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-quic" + "github.com/sagernet/sing-quic/congestion_bbr1" + "github.com/sagernet/sing-quic/congestion_bbr2" + congestion_meta1 "github.com/sagernet/sing-quic/congestion_meta1" + congestion_meta2 "github.com/sagernet/sing-quic/congestion_meta2" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/ntp" ) func init() { - naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) { + naive.ConfigureHTTP3ListenerFunc = func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error) { err := qtls.ConfigureHTTP3(tlsConfig) if err != nil { return nil, err @@ -26,9 +34,67 @@ func init() { return nil, err } + var congestionControl func(conn *quic.Conn) congestion.CongestionControl + switch options.QUICCongestionControl { + case "", "bbr": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_meta2.NewBbrSender( + congestion_meta2.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion.ByteCount(conn.Config().InitialPacketSize), + congestion.ByteCount(congestion_meta1.InitialCongestionWindow), + ) + } + case "bbr_standard": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_bbr1.NewBbrSender( + congestion_bbr1.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion.ByteCount(conn.Config().InitialPacketSize), + congestion_bbr1.InitialCongestionWindowPackets, + congestion_bbr1.MaxCongestionWindowPackets, + ) + } + case "bbr2": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_bbr2.NewBBR2Sender( + congestion_bbr2.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion.ByteCount(conn.Config().InitialPacketSize), + 0, + false, + ) + } + case "bbr2_variant": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_bbr2.NewBBR2Sender( + congestion_bbr2.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion.ByteCount(conn.Config().InitialPacketSize), + 32*congestion.ByteCount(conn.Config().InitialPacketSize), + true, + ) + } + case "cubic": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_meta1.NewCubicSender( + congestion_meta1.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion.ByteCount(conn.Config().InitialPacketSize), + false, + ) + } + case "reno": + congestionControl = func(conn *quic.Conn) congestion.CongestionControl { + return congestion_meta1.NewCubicSender( + congestion_meta1.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion.ByteCount(conn.Config().InitialPacketSize), + true, + ) + } + default: + return nil, E.New("unknown quic congestion control: ", options.QUICCongestionControl) + } + quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{ - MaxIncomingStreams: 1 << 60, - Allow0RTT: true, + MaxIncomingStreams: 1 << 60, + Allow0RTT: true, + GetCongestionControl: congestionControl, }) if err != nil { udpConn.Close() diff --git a/test/go.mod b/test/go.mod index 3e934f9e..9953b46a 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,9 +10,9 @@ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/gofrs/uuid/v5 v5.3.2 - github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 + github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 - github.com/sagernet/sing-quic v0.6.0-beta.5 + github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/spyzhov/ajson v0.9.4 @@ -97,31 +97,30 @@ require ( github.com/safchain/ethtool v0.3.0 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cors v1.2.1 // indirect - github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 // indirect - github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect + github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f // indirect + github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect diff --git a/test/go.sum b/test/go.sum index ebba7fe1..d7175af8 100644 --- a/test/go.sum +++ b/test/go.sum @@ -181,56 +181,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1 h1:ql2eCQp1sIinoSwNcJW+tBGToRoxm0rsU8uqRJA9Vao= -github.com/sagernet/cronet-go v0.0.1-140.0.7339.123-1/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= -github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f h1:eZcIuGEbGT9Ldh4QP14UxVzWOeMpwltU8lWCthefaGw= -github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:qt0I3+OORnNmYDKKg3sN2/JguaNNl3ckgdoYEMeMGSA= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 h1:JPrpsOAMZL7WpRfRymdazS/LTguAJVTqTPgIM1hRGSk= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 h1:yKOa/aPDWyB8+Vw2bze3lczwA0T+Jp014F82bb04RZg= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 h1:bFWOKRv1gadjBtcCj5eSMVITdM/nFAM9WRvHGrFkW2M= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 h1:rwk5kw6tggldlnYkufttjrLgNZ4m2oDj8VPVbUkMIj8= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Kz7898MkdKck/F3KhpQ3iSbDvuMcBKgcIT9kblbEAag= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 h1:Csm66Pf4aFnrKW9SQ7/QAD+ODxrXM1y/1tOsMeWJt2Q= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:xYt0vGrcBe5E/4S1AhZrXZKqzfyMVv2EJzms+hkyMwE= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 h1:HUIwo+G+gp5xJO7oFLXE8wUBuAMSlZCff7x8BkGwrV8= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:aM0ZlZyaMsj+ckqffWuwQ/LvlEocp4+8jf0CS3Y7C08= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 h1:vAFYo+d3JZauxyW2fL8Kqqzjx2CoMSNYwS3mj2O3vig= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 h1:vitvkHM3isRtAt6J/hJbi1YEkkmoJQYxCWFyWBzetK0= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Y5OW7wtkGkAICo1c4TjsSVsRe0YY0rGXBZS3W4kWEMM= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 h1:U4fj5bK6Bp/Fc3OaQT2+pXioRTC4NkWXD8M+kcJIXx0= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 h1:6aMDLdA+GF0J6CPBAXh6yn8wCJHhmREIU8I/uCVl16w= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 h1:OvZQkFRfs5fhN1ZNEjBRj1foZfwP9EdYhLFzrF23TO4= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 h1:HJYoVGlafnfo7qwsNgocIwqg/ScCWH3MwC7oqD4Hj3I= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 h1:U/eE0HxhS6iJxBhvt2mmkxvAuwjGQ6SfGz0IZc0NWQE= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:V8mKJHu2iMoHjXH2deNQAY+Itcn9mlTeLDqhrFXkwcY= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 h1:uHy+Un5hEvONSH5+nWHLWisvfhPIAE0EWq8cLjJpAuU= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:IXNOyK596srcY+OtnrCrUdclKVL5lakKcO30H85Kryc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 h1:aPMCg3h46G8Mx5WIiYyPTr/1EQ8j4wgCI/HUPGm/occ= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 h1:snfESeRYtwkOlfQyoxfHKVNj4N0cpIpxMfsM+y52rw4= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 h1:weCViGxMO6iajrI1EjHH4hMT/cXwGc+HhXH24xUrVGE= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f h1:WTHyVtd9nNZ4VB20aja31e0ZXXGrVlssAanJJBMc5BU= +github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f h1:qupezSQEMraq2yajI4HrWf9h9rY7RESUYbYHRRd49FY= +github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:onaFo5hJh1KsvuxkYTFLokh0Yx2oBh9J3yvFTnFB1Fc= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:jmDdDxVFN/W+pX/QRHYR6jFu5gosRPNSSPemGmchHpM= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:zQeDQJg2YZSlmR5+mYV4KyqIWwe+SH5hnxTO4EalDwA= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:85ck9+7Ftj+J3RO1uusn1Y3EXRLX9Dy0WIK/ZjRlQok= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:D//nc8RxHp7tTKcIYYQZ80wQBwxlCJv6HpkrCkqIVRo= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:wwrQCcrwnHSY/Y4cy8JCiZLzuWXM8tENphhJtFplh0c= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:Q52ojQVXk9f6Z+EWHWv+SJajNF56bVvgMyQP/7WV2TI= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:UzXQ09sZQZDlsZvWd4SAhtaF0RMxAVg4TEYsRNMutjU= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:sjSknfRi3fMJqwHW4pNL64SGq7Y5XIm4pz5Ds21hvKw= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:uxb8pqzB4KH7ai6MocOWQPu3/+BAYISSIMPHWs0wtrQ= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:uV78fIQ2XdVQUzowiafKP2fs+rGlkI+9u7l1cBMlQlc= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:6d87Pvio5dpWakic3SNBVTwTlt8ZnvL1GWPPeY2xSLs= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:tc3GdN9pAfZqdfb55DmtUSXEuKuecWNYSD/+4jclXmo= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:W5fN1B1wYgnh7Y5Nw+MgcDjsvdPvEPAGe27iemq2LJs= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:xfNH8CO8p2W8rtVFgyv8kiAkzoSArW6+h2s/p5ZNvP8= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:1mkhgxLOh1pPjBLgC/GK3Rhhinz0254OjGJRrivVhQ8= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:zf252WwVhJYduTDBXvkBKdwSU+Ce0OToQzvy5SwPS3c= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:Nhkn2M2UsGe0hr6qOgNKvGKgAbo6EfWQto+YjlNWGqg= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:HSvWFyBDLC7grFuSwBSIaxLI/MVpe/w4qJJAWAZsBxc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:PJa1fhmbXWXCYk07DZhqzjP2u9rM/cnS0A1aaPU56pY= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:Tpskq8p8boYHkBya18ythhgWTMv8gFsseGk55MFW+/k= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:qjB64lzTP0ROuk9GCCwsNQ9ca37nIr75iOyc2vuGFQg= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:GDJjygR/HLWOzwAs0RjhvqN6d1rUm7WiDAmHNe8gRAM= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= @@ -239,15 +237,15 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 h1:Rah/tDukrowqlznHQgXD4E9/yEsVsEMIxBZzS2NorGc= +github.com/sagernet/quic-go v0.57.1-sing-box-mod.3/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4= -github.com/sagernet/sing-quic v0.6.0-beta.5/go.mod h1:9D9GANrK33NjWCe1VkU5L5+8MxU39WrduBSmHuHz8GA= +github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 h1:YNXy008FFkVwfNjCR8GlVTlHbBXPwv8Y4ytDnSFUSDo= +github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0/go.mod h1:Y3YVjPutLHLQvYjGtUFH+w8YmM4MTd19NDzxLZGNGIs= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= diff --git a/test/naive_self_test.go b/test/naive_self_test.go index 0352d373..a40f2f78 100644 --- a/test/naive_self_test.go +++ b/test/naive_self_test.go @@ -210,7 +210,6 @@ func TestNaiveSelfPublicKeySHA256(t *testing.T) { } func TestNaiveSelfECH(t *testing.T) { - t.Skip("TODO: ECH is not currently supported on naive outbound") caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") caPemContent, err := os.ReadFile(caPem) require.NoError(t, err) @@ -440,3 +439,196 @@ func TestNaiveSelfInsecureConcurrency(t *testing.T) { require.GreaterOrEqual(t, sessionCount, 3, "Expected at least 3 HTTP/2 sessions with insecure_concurrency=3. NetLog: %s", netLogPath) } + +func TestNaiveSelfQUIC(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkUDP, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + QUIC: true, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + testTCP(t, clientPort, testPort) +} + +func TestNaiveSelfQUICCongestionControl(t *testing.T) { + testCases := []struct { + name string + congestionControl string + }{ + {"BBR", "bbr"}, + {"BBR2", "bbr2"}, + {"Cubic", "cubic"}, + {"Reno", "reno"}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + caPemContent, err := os.ReadFile(caPem) + require.NoError(t, err) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeNaive, + Tag: "naive-in", + Options: &option.NaiveInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: serverPort, + }, + Users: []auth.User{ + { + Username: "sekai", + Password: "password", + }, + }, + Network: network.NetworkUDP, + QUICCongestionControl: tc.congestionControl, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeNaive, + Tag: "naive-out", + Options: &option.NaiveOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Username: "sekai", + Password: "password", + QUIC: true, + QUICCongestionControl: tc.congestionControl, + OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + Certificate: []string{string(caPemContent)}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + Type: C.RuleTypeDefault, + DefaultOptions: option.DefaultRule{ + RawDefaultRule: option.RawDefaultRule{ + Inbound: []string{"mixed-in"}, + }, + RuleAction: option.RuleAction{ + Action: C.RuleActionTypeRoute, + RouteOptions: option.RouteActionOptions{ + Outbound: "naive-out", + }, + }, + }, + }, + }, + }, + }) + testTCP(t, clientPort, testPort) + }) + } +} From 750dc9c3e0903370778b1ea36aa1898f183996da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 18 Dec 2025 22:47:51 +0800 Subject: [PATCH 057/185] Fix naive network --- protocol/naive/outbound.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index ebaf373d..8bda26ec 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -204,8 +204,14 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL Version: uotOptions.Version, } } + var networks []string + if uotClient != nil { + networks = []string{N.NetworkTCP, N.NetworkUDP} + } else { + networks = []string{N.NetworkTCP} + } return &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, []string{N.NetworkTCP}, options.DialerOptions), + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeNaive, tag, networks, options.DialerOptions), ctx: ctx, logger: logger, client: client, From 4afdf4153a447ceaa1393ffdff9f8e1137c7e700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 18 Dec 2025 23:12:25 +0800 Subject: [PATCH 058/185] platform: Use new crash log api --- experimental/libbox/log.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/experimental/libbox/log.go b/experimental/libbox/log.go index d9c4c712..ff33f081 100644 --- a/experimental/libbox/log.go +++ b/experimental/libbox/log.go @@ -5,11 +5,10 @@ package libbox import ( "os" "runtime" - - "golang.org/x/sys/unix" + "runtime/debug" ) -var stderrFile *os.File +var crashOutputFile *os.File func RedirectStderr(path string) error { if stats, err := os.Stat(path); err == nil && stats.Size() > 0 { @@ -27,12 +26,12 @@ func RedirectStderr(path string) error { return err } } - err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd())) + err = debug.SetCrashOutput(outputFile, debug.CrashOptions{}) if err != nil { outputFile.Close() os.Remove(outputFile.Name()) return err } - stderrFile = outputFile + crashOutputFile = outputFile return nil } From 143983b5851e5811b6a3e490a3481fe065246853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 19 Dec 2025 17:32:55 +0800 Subject: [PATCH 059/185] Remove certificate_public_key_sha256 for naive --- .github/CRONET_GO_VERSION | 2 +- docs/configuration/outbound/naive.md | 4 +- docs/configuration/outbound/naive.zh.md | 4 +- go.mod | 48 +++++------ go.sum | 96 +++++++++++----------- protocol/naive/outbound.go | 31 ++++--- test/naive_self_test.go | 103 ------------------------ 7 files changed, 94 insertions(+), 194 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 74f690d2..73d4820b 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -446096b34fb07e86541d927be26f8e5c42d41b4a +5e61220bb2d22453eb6d105077c88c8ab1e1c3b2 diff --git a/docs/configuration/outbound/naive.md b/docs/configuration/outbound/naive.md index a56036d9..d9af4fb1 100644 --- a/docs/configuration/outbound/naive.md +++ b/docs/configuration/outbound/naive.md @@ -105,7 +105,9 @@ QUIC congestion control algorithm. TLS configuration, see [TLS](/configuration/shared/tls/#outbound). -Only `server_name`, `certificate`, `certificate_path`, `certificate_public_key_sha256` and `ech` are supported. +Only `server_name`, `certificate`, `certificate_path` and `ech` are supported. + +Self-signed certificates change traffic behavior significantly, which defeats the purpose of NaiveProxy's design to resist traffic analysis, and should not be used in production. ### Dial Fields diff --git a/docs/configuration/outbound/naive.zh.md b/docs/configuration/outbound/naive.zh.md index 40bcc2e5..07896407 100644 --- a/docs/configuration/outbound/naive.zh.md +++ b/docs/configuration/outbound/naive.zh.md @@ -105,7 +105,9 @@ QUIC 拥塞控制算法。 TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 -只有 `server_name`、`certificate`、`certificate_path`、`certificate_public_key_sha256` 和 `ech` 是被支持的。 +只有 `server_name`、`certificate`、`certificate_path` 和 `ech` 是被支持的。 + +自签名证书会显著改变流量行为,违背了 NaiveProxy 旨在抵抗流量分析的设计初衷,不应该在生产环境中使用。 ### 拨号字段 diff --git a/go.mod b/go.mod index 994922f2..4fe0bbd6 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f - github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f + github.com/sagernet/cronet-go v0.0.0-20251219080614-460b6a5fb79d + github.com/sagernet/cronet-go/all v0.0.0-20251219080614-460b6a5fb79d github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -108,28 +108,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index c2ceaafc..dbba5d36 100644 --- a/go.sum +++ b/go.sum @@ -153,54 +153,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f h1:WTHyVtd9nNZ4VB20aja31e0ZXXGrVlssAanJJBMc5BU= -github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f h1:qupezSQEMraq2yajI4HrWf9h9rY7RESUYbYHRRd49FY= -github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:onaFo5hJh1KsvuxkYTFLokh0Yx2oBh9J3yvFTnFB1Fc= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:jmDdDxVFN/W+pX/QRHYR6jFu5gosRPNSSPemGmchHpM= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:zQeDQJg2YZSlmR5+mYV4KyqIWwe+SH5hnxTO4EalDwA= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:85ck9+7Ftj+J3RO1uusn1Y3EXRLX9Dy0WIK/ZjRlQok= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:D//nc8RxHp7tTKcIYYQZ80wQBwxlCJv6HpkrCkqIVRo= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:wwrQCcrwnHSY/Y4cy8JCiZLzuWXM8tENphhJtFplh0c= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:Q52ojQVXk9f6Z+EWHWv+SJajNF56bVvgMyQP/7WV2TI= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:UzXQ09sZQZDlsZvWd4SAhtaF0RMxAVg4TEYsRNMutjU= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:sjSknfRi3fMJqwHW4pNL64SGq7Y5XIm4pz5Ds21hvKw= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:uxb8pqzB4KH7ai6MocOWQPu3/+BAYISSIMPHWs0wtrQ= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:uV78fIQ2XdVQUzowiafKP2fs+rGlkI+9u7l1cBMlQlc= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:6d87Pvio5dpWakic3SNBVTwTlt8ZnvL1GWPPeY2xSLs= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:tc3GdN9pAfZqdfb55DmtUSXEuKuecWNYSD/+4jclXmo= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:W5fN1B1wYgnh7Y5Nw+MgcDjsvdPvEPAGe27iemq2LJs= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:xfNH8CO8p2W8rtVFgyv8kiAkzoSArW6+h2s/p5ZNvP8= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:1mkhgxLOh1pPjBLgC/GK3Rhhinz0254OjGJRrivVhQ8= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:zf252WwVhJYduTDBXvkBKdwSU+Ce0OToQzvy5SwPS3c= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:Nhkn2M2UsGe0hr6qOgNKvGKgAbo6EfWQto+YjlNWGqg= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:HSvWFyBDLC7grFuSwBSIaxLI/MVpe/w4qJJAWAZsBxc= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:PJa1fhmbXWXCYk07DZhqzjP2u9rM/cnS0A1aaPU56pY= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:Tpskq8p8boYHkBya18ythhgWTMv8gFsseGk55MFW+/k= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:qjB64lzTP0ROuk9GCCwsNQ9ca37nIr75iOyc2vuGFQg= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:GDJjygR/HLWOzwAs0RjhvqN6d1rUm7WiDAmHNe8gRAM= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251219080614-460b6a5fb79d h1:Wfs4UEnHE3d0hHHG1NedCCwU2lUVlsgKblBGuOps8k4= +github.com/sagernet/cronet-go v0.0.0-20251219080614-460b6a5fb79d/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251219080614-460b6a5fb79d h1:DZy1I3zKP5bRaFGDYStZWAXrtlxn3W4ONbFUD027sw8= +github.com/sagernet/cronet-go/all v0.0.0-20251219080614-460b6a5fb79d/go.mod h1:eKeQLIddaQGZD2/jc0t+AdTnHve3Yo+uMujwgNOCfz0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251219080137-fbc9bf07b76b h1:+WRqXRiHEom7+lZUEA4qPVfNQB7Rx4++6JEN8cd5CgQ= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:ofD/ZwijuG5ccz0afLcvbfssIG+q/Bp8A2sziYYlJ28= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251219080137-fbc9bf07b76b h1:m3Hv8Hs6x6JdFNe93nzh1XhdZ7DmkfOKheloDmQmGQo= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:gA8bzjowa5Lmih+hBjjh6xsjghmGTIojKNtPzVDG93A= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:0ASeUCDj66dAHaCU/HkgbuSm/1co+yHbrq13BokW/us= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:NsbDT3L2pLS0Qsh0MPEn+IPkL33XoL7eFs56EA0jBeo= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:hIMIG2AuF3n9yelPvfUB3jyW2vWJnPiJfhE3hGUGJDk= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:/l1dS38ijnn0IHtAdFH3Y8cahAGw6ii0TYow0jUNBGM= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:m29qbSNfdNuTYEgTkph9TQUIyu7wIrY3PAvbgl23OjA= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251219080137-fbc9bf07b76b h1:mmOPED30mKTr0DucTYQeJBHtjAgvBO7huwdG3gL6Gyw= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251219080137-fbc9bf07b76b h1:y7V2c5QwMY07ME/M+cI/W1ZNw2QFT2oMw99GaPtK0MY= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:txNHLfT1miemCZgCrtrA+eTbGfsfN46wHIpOShALNzI= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251219080137-fbc9bf07b76b h1:FVPZ3TKAoIeM1hTCqUn0D7gHx6rDkKqhx+E8yaYNbXI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251219080137-fbc9bf07b76b h1:4ei7Sz97XCFXh+FUi2cKGHrhqD0A8lxdjZxST7NzDrQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:W0SrTDGPaKfcgjiZDkYeWqpICeVkwydaa228znP4q2c= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251219080137-fbc9bf07b76b h1:WD7V83I/FDatR0zXq6gSxh8gGZCD+fUZjLb1f+OVuF0= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251219080137-fbc9bf07b76b h1:M1V7j6Bke+v9aXeEK/NcWqoPwl84h97fmL3XSJJmNJw= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:DmLB0sgGw7vNao08a7NJ0xMJbZpeL49rAphknfRRs5I= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:j8tKUY2VVnK/RxIvX75G/nLGu53to6LaDRH/NTzyLDw= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:+1hjEBE2hUGUxDKtNg+gBYYCHXPQao273PjimC67v38= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:22Jx/y6cz2lVeUwbN/lmNOUjhEWYpohGEYpW/zdDcBk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:7juUKsCbreYSP8gALPf45kut0zdSpZXrk2TLIwQ2zOo= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index 8bda26ec..53e3e3a4 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -176,22 +176,21 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return nil, E.New("unknown quic congestion control: ", options.QUICCongestionControl) } client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{ - Context: ctx, - ServerAddress: serverAddress, - ServerName: serverName, - Username: options.Username, - Password: options.Password, - InsecureConcurrency: options.InsecureConcurrency, - ExtraHeaders: extraHeaders, - TrustedRootCertificates: trustedRootCertificates, - TrustedCertificatePublicKeySHA256: options.TLS.CertificatePublicKeySHA256, - Dialer: outboundDialer, - DNSResolver: dnsResolver, - ECHEnabled: echEnabled, - ECHConfigList: echConfigList, - ECHQueryServerName: echQueryServerName, - QUIC: options.QUIC, - QUICCongestionControl: quicCongestionControl, + Context: ctx, + ServerAddress: serverAddress, + ServerName: serverName, + Username: options.Username, + Password: options.Password, + InsecureConcurrency: options.InsecureConcurrency, + ExtraHeaders: extraHeaders, + TrustedRootCertificates: trustedRootCertificates, + Dialer: outboundDialer, + DNSResolver: dnsResolver, + ECHEnabled: echEnabled, + ECHConfigList: echConfigList, + ECHQueryServerName: echQueryServerName, + QUIC: options.QUIC, + QUICCongestionControl: quicCongestionControl, }) if err != nil { return nil, err diff --git a/test/naive_self_test.go b/test/naive_self_test.go index a40f2f78..873e7b9e 100644 --- a/test/naive_self_test.go +++ b/test/naive_self_test.go @@ -1,9 +1,6 @@ package main import ( - "crypto/sha256" - "crypto/x509" - "encoding/pem" "net/netip" "os" "strings" @@ -109,106 +106,6 @@ func TestNaiveSelf(t *testing.T) { testTCP(t, clientPort, testPort) } -func TestNaiveSelfPublicKeySHA256(t *testing.T) { - _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - - // Read and parse the server certificate to get its public key SHA256 - certPemContent, err := os.ReadFile(certPem) - require.NoError(t, err) - block, _ := pem.Decode(certPemContent) - require.NotNil(t, block) - cert, err := x509.ParseCertificate(block.Bytes) - require.NoError(t, err) - - // Calculate SHA256 of SPKI (Subject Public Key Info) - spkiBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) - require.NoError(t, err) - pinHash := sha256.Sum256(spkiBytes) - - startInstance(t, option.Options{ - Inbounds: []option.Inbound{ - { - Type: C.TypeMixed, - Tag: "mixed-in", - Options: &option.HTTPMixedInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), - ListenPort: clientPort, - }, - }, - }, - { - Type: C.TypeNaive, - Tag: "naive-in", - Options: &option.NaiveInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), - ListenPort: serverPort, - }, - Users: []auth.User{ - { - Username: "sekai", - Password: "password", - }, - }, - Network: network.NetworkTCP, - InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ - TLS: &option.InboundTLSOptions{ - Enabled: true, - ServerName: "example.org", - CertificatePath: certPem, - KeyPath: keyPem, - }, - }, - }, - }, - }, - Outbounds: []option.Outbound{ - { - Type: C.TypeDirect, - }, - { - Type: C.TypeNaive, - Tag: "naive-out", - Options: &option.NaiveOutboundOptions{ - ServerOptions: option.ServerOptions{ - Server: "127.0.0.1", - ServerPort: serverPort, - }, - Username: "sekai", - Password: "password", - OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{ - TLS: &option.OutboundTLSOptions{ - Enabled: true, - ServerName: "example.org", - CertificatePublicKeySHA256: [][]byte{pinHash[:]}, - }, - }, - }, - }, - }, - Route: &option.RouteOptions{ - Rules: []option.Rule{ - { - Type: C.RuleTypeDefault, - DefaultOptions: option.DefaultRule{ - RawDefaultRule: option.RawDefaultRule{ - Inbound: []string{"mixed-in"}, - }, - RuleAction: option.RuleAction{ - Action: C.RuleActionTypeRoute, - RouteOptions: option.RouteActionOptions{ - Outbound: "naive-out", - }, - }, - }, - }, - }, - }, - }) - testTCP(t, clientPort, testPort) -} - func TestNaiveSelfECH(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") caPemContent, err := os.ReadFile(caPem) From 2fc1b672cc12caaf88cc575f578af15709725a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 19 Dec 2025 18:00:23 +0800 Subject: [PATCH 060/185] documentation: Minor fixes --- docs/configuration/inbound/naive.md | 6 ++++++ docs/configuration/inbound/naive.zh.md | 6 ++++++ docs/configuration/shared/tls.md | 20 ++++++++++---------- docs/configuration/shared/tls.zh.md | 20 ++++++++++---------- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/docs/configuration/inbound/naive.md b/docs/configuration/inbound/naive.md index 05c5b9f9..a360fa95 100644 --- a/docs/configuration/inbound/naive.md +++ b/docs/configuration/inbound/naive.md @@ -1,3 +1,7 @@ +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [quic_congestion_control](#quic_congestion_control) + ### Structure ```json @@ -39,6 +43,8 @@ Naive users. #### quic_congestion_control +!!! question "Since sing-box 1.13.0" + QUIC congestion control algorithm. | Algorithm | Description | diff --git a/docs/configuration/inbound/naive.zh.md b/docs/configuration/inbound/naive.zh.md index bc2620b7..c9bfc917 100644 --- a/docs/configuration/inbound/naive.zh.md +++ b/docs/configuration/inbound/naive.zh.md @@ -1,3 +1,7 @@ +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [quic_congestion_control](#quic_congestion_control) + ### 结构 ```json @@ -39,6 +43,8 @@ Naive 用户。 #### quic_congestion_control +!!! question "Since sing-box 1.13.0" + QUIC 拥塞控制算法。 | 算法 | 描述 | diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 1350912f..0100e55a 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -4,16 +4,16 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [kernel_tx](#kernel_tx) - :material-plus: [kernel_rx](#kernel_rx) - :material-plus: [curve_preferences](#curve_preferences) - :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) - :material-plus: [client_certificate](#client_certificate) - :material-plus: [client_certificate_path](#client_certificate_path) - :material-plus: [client_key](#client_key) - :material-plus: [client_key_path](#client_key_path) - :material-plus: [client_authentication](#client_authentication) - :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) + :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [curve_preferences](#curve_preferences) + :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) + :material-plus: [client_certificate](#client_certificate) + :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_key](#client_key) + :material-plus: [client_key_path](#client_key_path) + :material-plus: [client_authentication](#client_authentication) + :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) :material-plus: [ech.query_server_name](#query_server_name) !!! quote "Changes in sing-box 1.12.0" diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 2d507f8a..4d1b6a75 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -4,16 +4,16 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [kernel_tx](#kernel_tx) - :material-plus: [kernel_rx](#kernel_rx) - :material-plus: [curve_preferences](#curve_preferences) - :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) - :material-plus: [client_certificate](#client_certificate) - :material-plus: [client_certificate_path](#client_certificate_path) - :material-plus: [client_key](#client_key) - :material-plus: [client_key_path](#client_key_path) - :material-plus: [client_authentication](#client_authentication) - :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) + :material-plus: [kernel_tx](#kernel_tx) + :material-plus: [kernel_rx](#kernel_rx) + :material-plus: [curve_preferences](#curve_preferences) + :material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256) + :material-plus: [client_certificate](#client_certificate) + :material-plus: [client_certificate_path](#client_certificate_path) + :material-plus: [client_key](#client_key) + :material-plus: [client_key_path](#client_key_path) + :material-plus: [client_authentication](#client_authentication) + :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256) :material-plus: [ech.query_server_name](#query_server_name) !!! quote "sing-box 1.12.0 中的更改" From faff3174a35ae31e48ed705c5f93974066cbe3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 20 Dec 2025 18:47:50 +0800 Subject: [PATCH 061/185] Add trace logging for lifecycle calls Log start/close operations with timing information for debugging. --- adapter/endpoint/manager.go | 22 ++++++++++++++--- adapter/inbound/manager.go | 22 ++++++++++++++--- adapter/lifecycle.go | 39 ++++++++++++++++++++++++++--- adapter/outbound/manager.go | 37 ++++++++++++++++++++++------ adapter/service/manager.go | 22 ++++++++++++++--- box.go | 49 ++++++++++++++++++++++++++++--------- 6 files changed, 156 insertions(+), 35 deletions(-) diff --git a/adapter/endpoint/manager.go b/adapter/endpoint/manager.go index 5a633bee..8b7c287f 100644 --- a/adapter/endpoint/manager.go +++ b/adapter/endpoint/manager.go @@ -4,6 +4,7 @@ import ( "context" "os" "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/taskmonitor" @@ -11,6 +12,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" ) var _ adapter.EndpointManager = (*Manager)(nil) @@ -46,10 +48,14 @@ func (m *Manager) Start(stage adapter.StartStage) error { return nil } for _, endpoint := range m.endpoints { + name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" + m.logger.Trace(stage, " ", name) + startTime := time.Now() err := adapter.LegacyStart(endpoint, stage) if err != nil { - return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } @@ -66,11 +72,15 @@ func (m *Manager) Close() error { monitor := taskmonitor.New(m.logger, C.StopTimeout) var err error for _, endpoint := range endpoints { - monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" + m.logger.Trace("close ", name) + startTime := time.Now() + monitor.Start("close ", name) err = E.Append(err, endpoint.Close(), func(err error) error { - return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + return E.Cause(err, "close ", name) }) monitor.Finish() + m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } @@ -119,11 +129,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. m.access.Lock() defer m.access.Unlock() if m.started { + name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" for _, stage := range adapter.ListStartStages { + m.logger.Trace(stage, " ", name) + startTime := time.Now() err = adapter.LegacyStart(endpoint, stage) if err != nil { - return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } } if existsEndpoint, loaded := m.endpointByTag[tag]; loaded { diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go index 89e424ac..438c20f4 100644 --- a/adapter/inbound/manager.go +++ b/adapter/inbound/manager.go @@ -4,6 +4,7 @@ import ( "context" "os" "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/taskmonitor" @@ -11,6 +12,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" ) var _ adapter.InboundManager = (*Manager)(nil) @@ -45,10 +47,14 @@ func (m *Manager) Start(stage adapter.StartStage) error { inbounds := m.inbounds m.access.Unlock() for _, inbound := range inbounds { + name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" + m.logger.Trace(stage, " ", name) + startTime := time.Now() err := adapter.LegacyStart(inbound, stage) if err != nil { - return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } @@ -65,11 +71,15 @@ func (m *Manager) Close() error { monitor := taskmonitor.New(m.logger, C.StopTimeout) var err error for _, inbound := range inbounds { - monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]") + name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" + m.logger.Trace("close ", name) + startTime := time.Now() + monitor.Start("close ", name) err = E.Append(err, inbound.Close(), func(err error) error { - return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]") + return E.Cause(err, "close ", name) }) monitor.Finish() + m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } @@ -121,11 +131,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. m.access.Lock() defer m.access.Unlock() if m.started { + name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" for _, stage := range adapter.ListStartStages { + m.logger.Trace(stage, " ", name) + startTime := time.Now() err = adapter.LegacyStart(inbound, stage) if err != nil { - return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } } if existsInbound, loaded := m.inboundByTag[tag]; loaded { diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go index face00b7..b969c98a 100644 --- a/adapter/lifecycle.go +++ b/adapter/lifecycle.go @@ -1,6 +1,14 @@ package adapter -import E "github.com/sagernet/sing/common/exceptions" +import ( + "reflect" + "strings" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) type SimpleLifecycle interface { Start() error @@ -48,22 +56,47 @@ type LifecycleService interface { Lifecycle } -func Start(stage StartStage, services ...Lifecycle) error { +func getServiceName(service any) string { + if named, ok := service.(interface { + Type() string + Tag() string + }); ok { + tag := named.Tag() + if tag != "" { + return named.Type() + "[" + tag + "]" + } + return named.Type() + } + t := reflect.TypeOf(service) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return strings.ToLower(t.Name()) +} + +func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error { for _, service := range services { + name := getServiceName(service) + logger.Trace(stage, " ", name) + startTime := time.Now() err := service.Start(stage) if err != nil { return err } + logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } -func StartNamed(stage StartStage, services []LifecycleService) error { +func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error { for _, service := range services { + logger.Trace(stage, " ", service.Name()) + startTime := time.Now() err := service.Start(stage) if err != nil { return E.Cause(err, stage.String(), " ", service.Name()) } + logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index 0fdeb390..5c1b5d99 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -6,6 +6,7 @@ import ( "os" "strings" "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/taskmonitor" @@ -13,6 +14,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" ) @@ -81,10 +83,14 @@ func (m *Manager) Start(stage adapter.StartStage) error { outbounds := m.outbounds m.access.Unlock() for _, outbound := range outbounds { + name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + m.logger.Trace(stage, " ", name) + startTime := time.Now() err := adapter.LegacyStart(outbound, stage) if err != nil { - return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } } return nil @@ -109,22 +115,29 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { } started[outboundTag] = true canContinue = true + name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]" if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter { - monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + m.logger.Trace("start ", name) + startTime := time.Now() + monitor.Start("start ", name) err := starter.Start(adapter.StartStateStart) monitor.Finish() if err != nil { - return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + return E.Cause(err, "start ", name) } + m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } else if starter, isStarter := outboundToStart.(interface { Start() error }); isStarter { - monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + m.logger.Trace("start ", name) + startTime := time.Now() + monitor.Start("start ", name) err := starter.Start() monitor.Finish() if err != nil { - return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]") + return E.Cause(err, "start ", name) } + m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } } if len(started) == len(outbounds) { @@ -171,11 +184,15 @@ func (m *Manager) Close() error { var err error for _, outbound := range outbounds { if closer, isCloser := outbound.(io.Closer); isCloser { - monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]") + name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + m.logger.Trace("close ", name) + startTime := time.Now() + monitor.Start("close ", name) err = E.Append(err, closer.Close(), func(err error) error { - return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]") + return E.Cause(err, "close ", name) }) monitor.Finish() + m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } } return nil @@ -256,11 +273,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log. return err } if m.started { + name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" for _, stage := range adapter.ListStartStages { + m.logger.Trace(stage, " ", name) + startTime := time.Now() err = adapter.LegacyStart(outbound, stage) if err != nil { - return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } } m.access.Lock() diff --git a/adapter/service/manager.go b/adapter/service/manager.go index d58b1a77..f17aa07e 100644 --- a/adapter/service/manager.go +++ b/adapter/service/manager.go @@ -4,6 +4,7 @@ import ( "context" "os" "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/taskmonitor" @@ -11,6 +12,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" ) var _ adapter.ServiceManager = (*Manager)(nil) @@ -43,10 +45,14 @@ func (m *Manager) Start(stage adapter.StartStage) error { services := m.services m.access.Unlock() for _, service := range services { + name := "service/" + service.Type() + "[" + service.Tag() + "]" + m.logger.Trace(stage, " ", name) + startTime := time.Now() err := adapter.LegacyStart(service, stage) if err != nil { - return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } @@ -63,11 +69,15 @@ func (m *Manager) Close() error { monitor := taskmonitor.New(m.logger, C.StopTimeout) var err error for _, service := range services { - monitor.Start("close service/", service.Type(), "[", service.Tag(), "]") + name := "service/" + service.Type() + "[" + service.Tag() + "]" + m.logger.Trace("close ", name) + startTime := time.Now() + monitor.Start("close ", name) err = E.Append(err, service.Close(), func(err error) error { - return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]") + return E.Cause(err, "close ", name) }) monitor.Finish() + m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } @@ -116,11 +126,15 @@ func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag stri m.access.Lock() defer m.access.Unlock() if m.started { + name := "service/" + service.Type() + "[" + service.Tag() + "]" for _, stage := range adapter.ListStartStages { + m.logger.Trace(stage, " ", name) + startTime := time.Now() err = adapter.LegacyStart(service, stage) if err != nil { - return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]") + return E.Cause(err, stage, " ", name) } + m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } } if existsService, loaded := m.serviceByTag[tag]; loaded { diff --git a/box.go b/box.go index 1c168820..7885b0d4 100644 --- a/box.go +++ b/box.go @@ -443,15 +443,15 @@ func (s *Box) preStart() error { if err != nil { return E.Cause(err, "start logger") } - err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api + err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api if err != nil { return err } - err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service) + err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service) if err != nil { return err } - err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router) + err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router) if err != nil { return err } @@ -463,27 +463,27 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.StartNamed(adapter.StartStateStart, s.internalService) + err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService) if err != nil { return err } - err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service) + err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service) if err != nil { return err } - err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service) + err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service) if err != nil { return err } - err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService) + err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService) if err != nil { return err } - err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service) + err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service) if err != nil { return err } - err = adapter.StartNamed(adapter.StartStateStarted, s.internalService) + err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService) if err != nil { return err } @@ -497,17 +497,42 @@ func (s *Box) Close() error { default: close(s.done) } - err := common.Close( - s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network, - ) + var err error + for _, closeItem := range []struct { + name string + service adapter.Lifecycle + }{ + {"service", s.service}, + {"endpoint", s.endpoint}, + {"inbound", s.inbound}, + {"outbound", s.outbound}, + {"router", s.router}, + {"connection", s.connection}, + {"dns-router", s.dnsRouter}, + {"dns-transport", s.dnsTransport}, + {"network", s.network}, + } { + s.logger.Trace("close ", closeItem.name) + startTime := time.Now() + err = E.Append(err, closeItem.service.Close(), func(err error) error { + return E.Cause(err, "close ", closeItem.name) + }) + s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") + } for _, lifecycleService := range s.internalService { + s.logger.Trace("close ", lifecycleService.Name()) + startTime := time.Now() err = E.Append(err, lifecycleService.Close(), func(err error) error { return E.Cause(err, "close ", lifecycleService.Name()) }) + s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } + s.logger.Trace("close logger") + startTime := time.Now() err = E.Append(err, s.logFactory.Close(), func(err error) error { return E.Cause(err, "close logger") }) + s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") return err } From 0d8c7a9c5d513be1ccca1eebaf1d0f166e8fa904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 20 Dec 2025 20:35:02 +0800 Subject: [PATCH 062/185] Fix cronet-go crash --- .github/CRONET_GO_VERSION | 2 +- go.mod | 48 ++++++++++---------- go.sum | 96 +++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 73d4820b..f887e9a2 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -5e61220bb2d22453eb6d105077c88c8ab1e1c3b2 +6228b14f72eea9db301d1fbe771c7bdecadefb40 diff --git a/go.mod b/go.mod index 4fe0bbd6..7a3727fa 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251219080614-460b6a5fb79d - github.com/sagernet/cronet-go/all v0.0.0-20251219080614-460b6a5fb79d + github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a + github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -108,28 +108,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251219080137-fbc9bf07b76b // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251219080137-fbc9bf07b76b // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/go.sum b/go.sum index dbba5d36..5412118a 100644 --- a/go.sum +++ b/go.sum @@ -153,54 +153,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251219080614-460b6a5fb79d h1:Wfs4UEnHE3d0hHHG1NedCCwU2lUVlsgKblBGuOps8k4= -github.com/sagernet/cronet-go v0.0.0-20251219080614-460b6a5fb79d/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251219080614-460b6a5fb79d h1:DZy1I3zKP5bRaFGDYStZWAXrtlxn3W4ONbFUD027sw8= -github.com/sagernet/cronet-go/all v0.0.0-20251219080614-460b6a5fb79d/go.mod h1:eKeQLIddaQGZD2/jc0t+AdTnHve3Yo+uMujwgNOCfz0= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251219080137-fbc9bf07b76b h1:+WRqXRiHEom7+lZUEA4qPVfNQB7Rx4++6JEN8cd5CgQ= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:ofD/ZwijuG5ccz0afLcvbfssIG+q/Bp8A2sziYYlJ28= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251219080137-fbc9bf07b76b h1:m3Hv8Hs6x6JdFNe93nzh1XhdZ7DmkfOKheloDmQmGQo= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:gA8bzjowa5Lmih+hBjjh6xsjghmGTIojKNtPzVDG93A= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:0ASeUCDj66dAHaCU/HkgbuSm/1co+yHbrq13BokW/us= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:NsbDT3L2pLS0Qsh0MPEn+IPkL33XoL7eFs56EA0jBeo= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:hIMIG2AuF3n9yelPvfUB3jyW2vWJnPiJfhE3hGUGJDk= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:/l1dS38ijnn0IHtAdFH3Y8cahAGw6ii0TYow0jUNBGM= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:m29qbSNfdNuTYEgTkph9TQUIyu7wIrY3PAvbgl23OjA= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251219080137-fbc9bf07b76b h1:mmOPED30mKTr0DucTYQeJBHtjAgvBO7huwdG3gL6Gyw= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251219080137-fbc9bf07b76b h1:y7V2c5QwMY07ME/M+cI/W1ZNw2QFT2oMw99GaPtK0MY= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:txNHLfT1miemCZgCrtrA+eTbGfsfN46wHIpOShALNzI= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251219080137-fbc9bf07b76b h1:FVPZ3TKAoIeM1hTCqUn0D7gHx6rDkKqhx+E8yaYNbXI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251219080137-fbc9bf07b76b h1:4ei7Sz97XCFXh+FUi2cKGHrhqD0A8lxdjZxST7NzDrQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:W0SrTDGPaKfcgjiZDkYeWqpICeVkwydaa228znP4q2c= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251219080137-fbc9bf07b76b h1:WD7V83I/FDatR0zXq6gSxh8gGZCD+fUZjLb1f+OVuF0= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251219080137-fbc9bf07b76b h1:M1V7j6Bke+v9aXeEK/NcWqoPwl84h97fmL3XSJJmNJw= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:DmLB0sgGw7vNao08a7NJ0xMJbZpeL49rAphknfRRs5I= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:j8tKUY2VVnK/RxIvX75G/nLGu53to6LaDRH/NTzyLDw= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b h1:+1hjEBE2hUGUxDKtNg+gBYYCHXPQao273PjimC67v38= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251219080137-fbc9bf07b76b h1:22Jx/y6cz2lVeUwbN/lmNOUjhEWYpohGEYpW/zdDcBk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251219080137-fbc9bf07b76b h1:7juUKsCbreYSP8gALPf45kut0zdSpZXrk2TLIwQ2zOo= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251219080137-fbc9bf07b76b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a h1:EdIrRa0yH3ZaLOfX95RYYiN0etpX3b9+jcsW/D8jCyQ= +github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a h1:GU/oBPVjyrCVb2l0SI5qtF6aUlLsnE4u4ehXbS/NF+0= +github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a/go.mod h1:4MT0juyKK0lIVwa6+6xLbRMFJBUIqRZOx70iMvq5vf0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e h1:EAcY0ZK8DWTNUdVCKdBQfXRsfGsohsh2ae9jIjlri2o= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:9luoNSy9Ej8rC/nZejzrP0uxX+BxiNxjLJ7XPYlApQg= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e h1:CSWZTuMlkO/98RSQpqC1EHtc9eSBzVmvMdPTnaFSDCM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:vkf3GDA11NtGm6XQHyP4tgWK3GD426ObmshdKdxGMhA= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:pNyNjDC5XPC6+2yNqiDA8QL8IYSsBJpaSLwPi/jY4JQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:2yDmTzppvxWuwxo4oDjxRzjlXBzZ6O3OCPWYeQcJRrw= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:+lyrRlxlKBYT/3LcYMaFHilUnRlKn3OQrImlWCey+qM= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:dBeeUaCPG0Y26FfCPO1o3v72yw2pSjqopryAW0uV354= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:JkGEqkfeglehN9tu+a0gHTq9Dmm+pY2wtjY+NiUdjgw= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e h1:rd/AbuuEMCHcA6ib5JBkQmWvonnoGwVc0/P0sHcWfAQ= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e h1:4XcwU6KOCkVOAr2EKKY8geYm7ctKCLlb1SsmFWMMUzE= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:fYDhPtxvMtZxBUM0k6GcaxbynLxMG2iTmQL3XcLtjS8= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:F5Vzd50H/AaoSHvt81yXZeUwDRKTcFjO/+KVW6VqbUs= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e h1:Ok/Eh8ajcvxlu7FAWk+lHAPLcSRDKrKkgq30o6gCR6M= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:JoohVAfcwE9HjpwAaTULtEOFZ1Mz0Zz5K4pOcrRjqwo= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:ULxfoxcNRA96QDDRLmObH4adbeUGtudH/2HlL4TgaNY= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e h1:NnhScXJyx4Fg+pV/WtkC1mD6+VR7D/Q+PMU4xGRvdRQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:INcIfjzUl0qgtZkt8OcVV537GoApcBsdegDl8yNJEdQ= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:jrialJ4U7BW9xyGI/JdUiigZ9hgyF1EayFKCH4pZxdw= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:XefHhnALVHU4ZgICNq6ZJl78bWDQO4nHwoWn+Ar7e0g= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:djShWO5vN0CamyboNDyNRDxL0/9oMtKjCDesqolIAsg= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:AgKqyS5zfRgDc8uprxUh+XCwwzJCj31KAFPjzyLRWs0= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= From cba18635c8fc613dc97ca8fad68d0e18159e1fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 21 Dec 2025 15:13:24 +0800 Subject: [PATCH 063/185] Add Chrome Root Store certificate option Adds `chrome` as a new certificate store option alongside `mozilla`. Both stores filter out China-based CA certificates. --- cmd/internal/update_certificates/main.go | 95 + common/certificate/chrome.go | 2817 ++++++++++++++++++++ common/certificate/store.go | 2 + constant/certificate.go | 1 + docs/configuration/certificate/index.md | 13 +- docs/configuration/certificate/index.zh.md | 15 +- 6 files changed, 2934 insertions(+), 9 deletions(-) create mode 100644 common/certificate/chrome.go diff --git a/cmd/internal/update_certificates/main.go b/cmd/internal/update_certificates/main.go index 744d7724..55b221e1 100644 --- a/cmd/internal/update_certificates/main.go +++ b/cmd/internal/update_certificates/main.go @@ -17,6 +17,10 @@ func main() { if err != nil { log.Error(err) } + err = updateChromeIncludedRootCAs() + if err != nil { + log.Error(err) + } } func updateMozillaIncludedRootCAs() error { @@ -69,3 +73,94 @@ func init() { generated.WriteString("}\n") return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644) } + +func fetchChinaFingerprints() (map[string]bool, error) { + response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/AllCertificateRecordsCSVFormatv4") + if err != nil { + return nil, err + } + defer response.Body.Close() + reader := csv.NewReader(response.Body) + header, err := reader.Read() + if err != nil { + return nil, err + } + countryIndex := slices.Index(header, "Country") + fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint") + + chinaFingerprints := make(map[string]bool) + for { + record, err := reader.Read() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + if record[countryIndex] == "China" { + chinaFingerprints[record[fingerprintIndex]] = true + } + } + return chinaFingerprints, nil +} + +func updateChromeIncludedRootCAs() error { + chinaFingerprints, err := fetchChinaFingerprints() + if err != nil { + return err + } + + response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/RootCACertificatesIncludedByRSReportCSV") + if err != nil { + return err + } + defer response.Body.Close() + reader := csv.NewReader(response.Body) + header, err := reader.Read() + if err != nil { + return err + } + subjectIndex := slices.Index(header, "Subject") + statusIndex := slices.Index(header, "Google Chrome Status") + certIndex := slices.Index(header, "X.509 Certificate (PEM)") + fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint") + + generated := strings.Builder{} + generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +import "crypto/x509" + +var chromeIncluded *x509.CertPool + +func init() { + chromeIncluded = x509.NewCertPool() +`) + for { + record, err := reader.Read() + if err == io.EOF { + break + } else if err != nil { + return err + } + if record[statusIndex] != "Included" { + continue + } + if chinaFingerprints[record[fingerprintIndex]] { + continue + } + generated.WriteString("\n // ") + generated.WriteString(record[subjectIndex]) + generated.WriteString("\n") + generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`") + cert := record[certIndex] + // Remove single quotes if present + if len(cert) > 0 && cert[0] == '\'' { + cert = cert[1 : len(cert)-1] + } + generated.WriteString(cert) + generated.WriteString("`))\n") + } + generated.WriteString("}\n") + return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644) +} diff --git a/common/certificate/chrome.go b/common/certificate/chrome.go new file mode 100644 index 00000000..8a361c61 --- /dev/null +++ b/common/certificate/chrome.go @@ -0,0 +1,2817 @@ +// Code generated by 'make update_certificates'. DO NOT EDIT. + +package certificate + +import "crypto/x509" + +var chromeIncluded *x509.CertPool + +func init() { + chromeIncluded = x509.NewCertPool() + + // CN=Actalis Authentication Root CA; O=Actalis S.p.A./03358520967; L=Milan; C=IT + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE-----`)) + + // CN=TunTrust Root CA; O=Agence Nationale de Certification Electronique; C=TN + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE-----`)) + + // CN=Amazon Root CA 4; O=Amazon; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE-----`)) + + // CN=Amazon Root CA 1; O=Amazon; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE-----`)) + + // CN=Amazon Root CA 2; O=Amazon; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE-----`)) + + // CN=Amazon Root CA 3; O=Amazon; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE-----`)) + + // CN=Certum Trusted Network CA; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE-----`)) + + // CN=Certum EC-384 CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE-----`)) + + // CN=Certum Trusted Root CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE-----`)) + + // CN=Certum Trusted Network CA 2; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE-----`)) + + // CN=Autoridad de Certificacion Firmaprofesional CIF A62634068; C=ES + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE-----`)) + + // CN=ANF Secure Server Root CA; OU=ANF CA Raiz; O=ANF Autoridad de Certificacion; C=ES; SerialNumber=G63287510 + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE-----`)) + + // CN=Buypass Class 2 Root CA; O=Buypass AS-983163327; C=NO + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE-----`)) + + // CN=Buypass Class 3 Root CA; O=Buypass AS-983163327; C=NO + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE-----`)) + + // CN=Certainly Root R1; O=Certainly; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE-----`)) + + // CN=Certainly Root E1; O=Certainly; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE-----`)) + + // CN=Certigna; O=Dhimyotis; C=FR + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE-----`)) + + // CN=Certigna Root CA; OU=0002 48146308100036; O=Dhimyotis; C=FR + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE-----`)) + + // OU=certSIGN ROOT CA; O=certSIGN; C=RO + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE-----`)) + + // OU=certSIGN ROOT CA G2; O=CERTSIGN SA; C=RO + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE-----`)) + + // CN=HiPKI Root CA - G1; O=Chunghwa Telecom Co., Ltd.; C=TW + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE-----`)) + + // OU=ePKI Root Certification Authority; O=Chunghwa Telecom Co., Ltd.; C=TW + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE-----`)) + + // CN=D-TRUST BR Root CA 1 2020; O=D-Trust GmbH; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE-----`)) + + // CN=D-TRUST EV Root CA 1 2020; O=D-Trust GmbH; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE-----`)) + + // CN=D-TRUST Root Class 3 CA 2 EV 2009; O=D-Trust GmbH; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE-----`)) + + // CN=D-TRUST Root Class 3 CA 2 2009; O=D-Trust GmbH; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE-----`)) + + // CN=T-TeleSec GlobalRoot Class 3; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE-----`)) + + // CN=T-TeleSec GlobalRoot Class 2; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE-----`)) + + // CN=DigiCert TLS RSA4096 Root G5; O=DigiCert, Inc.; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE-----`)) + + // CN=DigiCert TLS ECC P384 Root G5; O=DigiCert, Inc.; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE-----`)) + + // CN=DigiCert Assured ID Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE-----`)) + + // CN=DigiCert Assured ID Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE-----`)) + + // CN=DigiCert Assured ID Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE-----`)) + + // CN=DigiCert Global Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE-----`)) + + // CN=DigiCert Global Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE-----`)) + + // CN=DigiCert Global Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE-----`)) + + // CN=DigiCert High Assurance EV Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE-----`)) + + // CN=DigiCert Trusted Root G4; OU=www.digicert.com; O=DigiCert Inc; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE-----`)) + + // CN=QuoVadis Root CA 2; O=QuoVadis Limited; C=BM + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE-----`)) + + // CN=QuoVadis Root CA 2 G3; O=QuoVadis Limited; C=BM + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE-----`)) + + // CN=QuoVadis Root CA 3 G3; O=QuoVadis Limited; C=BM + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE-----`)) + + // CN=CA Disig Root R2; O=Disig a.s.; L=Bratislava; C=SK + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE-----`)) + + // CN=emSign ECC Root CA - G3; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE-----`)) + + // CN=emSign Root CA - G1; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE-----`)) + + // CN=AffirmTrust Commercial; O=AffirmTrust; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE-----`)) + + // CN=Atos TrustedRoot 2011; O=Atos; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE-----`)) + + // CN=Atos TrustedRoot Root CA ECC TLS 2021; O=Atos; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE-----`)) + + // CN=Atos TrustedRoot Root CA RSA TLS 2021; O=Atos; C=DE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE-----`)) + + // CN=GlobalSign; OU=GlobalSign Root CA - R6; O=GlobalSign + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE-----`)) + + // CN=GlobalSign Root E46; O=GlobalSign nv-sa; C=BE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE-----`)) + + // CN=GlobalSign Root R46; O=GlobalSign nv-sa; C=BE + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE-----`)) + + // CN=GlobalSign; OU=GlobalSign ECC Root CA - R5; O=GlobalSign + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE-----`)) + + // CN=GlobalSign; OU=GlobalSign Root CA - R3; O=GlobalSign + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE-----`)) + + // CN=Starfield Root Certificate Authority - G2; O=Starfield Technologies, Inc.; L=Scottsdale; ST=Arizona; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE-----`)) + + // CN=Go Daddy Root Certificate Authority - G2; O=GoDaddy.com, Inc.; L=Scottsdale; ST=Arizona; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE-----`)) + + // CN=GlobalSign; OU=GlobalSign ECC Root CA - R4; O=GlobalSign + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE-----`)) + + // CN=GTS Root R4; O=Google Trust Services LLC; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE-----`)) + + // CN=GTS Root R2; O=Google Trust Services LLC; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt +nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY +6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu +MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k +RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg +f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV ++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo +dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa +G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq +gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H +vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC +B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u +NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg +yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev +HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 +xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR +TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg +JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV +7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl +6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL +-----END CERTIFICATE-----`)) + + // CN=GTS Root R1; O=Google Trust Services LLC; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE-----`)) + + // CN=GTS Root R3; O=Google Trust Services LLC; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE-----`)) + + // CN=ACCVRAIZ1; OU=PKIACCV; O=ACCV; C=ES + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE-----`)) + + // OU=AC RAIZ FNMT-RCM; O=FNMT-RCM; C=ES + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE-----`)) + + // CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS; OU=Ceres; O=FNMT-RCM; C=ES; OrganizationIdentifier=VATES-Q2826004J + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE-----`)) + + // CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1; OU=Kamu Sertifikasyon Merkezi - Kamu SM; O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK; L=Gebze - Kocaeli; C=TR + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE-----`)) + + // CN=HARICA TLS RSA Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE-----`)) + + // CN=HARICA TLS ECC Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE-----`)) + + // CN=IdenTrust Commercial Root CA 1; O=IdenTrust; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE-----`)) + + // CN=ISRG Root X1; O=Internet Security Research Group; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE-----`)) + + // CN=ISRG Root X2; O=Internet Security Research Group; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE-----`)) + + // CN=Izenpe.com; O=IZENPE S.A.; C=ES + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE-----`)) + + // CN=SZAFIR ROOT CA2; O=Krajowa Izba Rozliczeniowa S.A.; C=PL + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE-----`)) + + // CN=e-Szigno Root CA 2017; O=Microsec Ltd.; L=Budapest; C=HU; OrganizationIdentifier=VATHU-23584497 + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE-----`)) + + // CN=Microsec e-Szigno Root CA 2009; O=Microsec Ltd.; L=Budapest; C=HU; EmailAddress=info@e-szigno.hu + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE-----`)) + + // CN=Microsoft ECC Root Certificate Authority 2017; O=Microsoft Corporation; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE-----`)) + + // CN=Microsoft RSA Root Certificate Authority 2017; O=Microsoft Corporation; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE-----`)) + + // CN=NAVER Global Root Certification Authority; O=NAVER BUSINESS PLATFORM Corp.; C=KR + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE-----`)) + + // CN=NetLock Arany (Class Gold) Főtanúsítvány; OU=Tanúsítványkiadók (Certification Services); O=NetLock Kft.; L=Budapest; C=HU + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE-----`)) + + // CN=OISTE WISeKey Global Root GC CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE-----`)) + + // CN=OISTE WISeKey Global Root GB CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE-----`)) + + // CN=Security Communication ECC RootCA1; O=SECOM Trust Systems CO.,LTD.; C=JP + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE-----`)) + + // OU=Security Communication RootCA2; O=SECOM Trust Systems CO.,LTD.; C=JP + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE-----`)) + + // CN=Entrust Root Certification Authority; OU=www.entrust.net/CPS is incorporated by reference, (c) 2006 Entrust, Inc.; O=Entrust, Inc.; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE-----`)) + + // CN=Sectigo Public Server Authentication Root E46; O=Sectigo Limited; C=GB + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE-----`)) + + // CN=COMODO ECC Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE-----`)) + + // CN=COMODO Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMTAxMDEwMDAw +MDBaFw0zMDEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo0IwQDAdBgNVHQ4EFgQUC1jli8ZMFTekQKkwqSG+RzZaVv8w +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBAC/JxBwHO89hAgCx2SFRdXIDMLDEFh9sAIsQrK/xR9SuEDwMGvjUk2ysEDd8 +t6aDZK3N3w6HM503sMZ7OHKx8xoOo/lVem0DZgMXlUrxsXrfViEGQo+x06iF3u6X +HWLrp+cxEmbDD6ZLLkGC9/3JG6gbr+48zuOcrigHoSybJMIPIyaDMouGDx8rEkYl +Fo92kANr3ryqImhrjKGsKxE5pttwwn1y6TPn/CbxdFqR5p2ErPioBhlG5qfpqjQi +pKGfeq23sqSaM4hxAjwu1nqyH6LKwN0vEJT9s4yEIHlG1QXUEOTS22RPuFvuG8Ug +R1uUq27UlTMdphVx8fiUylQ5PsE= +-----END CERTIFICATE-----`)) + + // CN=COMODO RSA Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE-----`)) + + // CN=USERTrust RSA Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE-----`)) + + // CN=USERTrust ECC Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE-----`)) + + // CN=Sectigo Public Server Authentication Root R46; O=Sectigo Limited; C=GB + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE-----`)) + + // CN=Entrust Root Certification Authority - G2; OU=See www.entrust.net/legal-terms, (c) 2009 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE-----`)) + + // CN=Entrust Root Certification Authority - EC1; OU=See www.entrust.net/legal-terms, (c) 2012 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE-----`)) + + // CN=SSL.com Root Certification Authority RSA; O=SSL Corporation; L=Houston; ST=Texas; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE-----`)) + + // CN=SSL.com TLS ECC Root CA 2022; O=SSL Corporation; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE-----`)) + + // CN=SSL.com TLS RSA Root CA 2022; O=SSL Corporation; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE-----`)) + + // CN=SSL.com Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE-----`)) + + // CN=SSL.com EV Root Certification Authority RSA R2; O=SSL Corporation; L=Houston; ST=Texas; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE-----`)) + + // CN=SSL.com EV Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE-----`)) + + // CN=SwissSign Gold CA - G2; O=SwissSign AG; C=CH + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE-----`)) + + // CN=TWCA CYBER Root CA; OU=Root CA; O=TAIWAN-CA; C=TW + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE-----`)) + + // CN=TWCA Global Root CA; OU=Root CA; O=TAIWAN-CA; C=TW + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE-----`)) + + // CN=TeliaSonera Root CA v1; O=TeliaSonera + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE-----`)) + + // CN=Telia Root CA v2; O=Telia Finland Oyj; C=FI + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE-----`)) + + // CN=Trustwave Global ECC P384 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE-----`)) + + // CN=Trustwave Global ECC P256 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE-----`)) + + // CN=SecureTrust CA; O=SecureTrust Corporation; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE-----`)) + + // CN=Trustwave Global Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US + chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE-----`)) +} diff --git a/common/certificate/store.go b/common/certificate/store.go index 82ce8e29..cfced463 100644 --- a/common/certificate/store.go +++ b/common/certificate/store.go @@ -53,6 +53,8 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific } case C.CertificateStoreMozilla: systemPool = mozillaIncluded + case C.CertificateStoreChrome: + systemPool = chromeIncluded case C.CertificateStoreNone: systemPool = nil default: diff --git a/constant/certificate.go b/constant/certificate.go index 7138242c..05ab1616 100644 --- a/constant/certificate.go +++ b/constant/certificate.go @@ -3,5 +3,6 @@ package constant const ( CertificateStoreSystem = "system" CertificateStoreMozilla = "mozilla" + CertificateStoreChrome = "chrome" CertificateStoreNone = "none" ) diff --git a/docs/configuration/certificate/index.md b/docs/configuration/certificate/index.md index 698fec70..88d73380 100644 --- a/docs/configuration/certificate/index.md +++ b/docs/configuration/certificate/index.md @@ -4,6 +4,10 @@ icon: material/new-box !!! question "Since sing-box 1.12.0" +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [Chrome Root Store](#store) + # Certificate ### Structure @@ -27,11 +31,12 @@ icon: material/new-box The default X509 trusted CA certificate list. -| Type | Description | -|--------------------|---------------------------------------------------------------------------------------------------------------| -| `system` (default) | System trusted CA certificates | +| Type | Description | +|--------------------|----------------------------------------------------------------------------------------------------------------| +| `system` (default) | System trusted CA certificates | | `mozilla` | [Mozilla Included List](https://wiki.mozilla.org/CA/Included_Certificates) with China CA certificates removed | -| `none` | Empty list | +| `chrome` | [Chrome Root Store](https://g.co/chrome/root-policy) with China CA certificates removed | +| `none` | Empty list | #### certificate diff --git a/docs/configuration/certificate/index.zh.md b/docs/configuration/certificate/index.zh.md index 9572e9cd..77f3fd88 100644 --- a/docs/configuration/certificate/index.zh.md +++ b/docs/configuration/certificate/index.zh.md @@ -4,6 +4,10 @@ icon: material/new-box !!! question "自 sing-box 1.12.0 起" +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [Chrome Root Store](#store) + # 证书 ### 结构 @@ -27,11 +31,12 @@ icon: material/new-box 默认的 X509 受信任 CA 证书列表。 -| 类型 | 描述 | -|--------------------|--------------------------------------------------------------------------------------------| -| `system`(默认) | 系统受信任的 CA 证书 | -| `mozilla` | [Mozilla 包含列表](https://wiki.mozilla.org/CA/Included_Certificates)(已移除中国 CA 证书) | -| `none` | 空列表 | +| 类型 | 描述 | +|-------------------|--------------------------------------------------------------------------------------------| +| `system`(默认) | 系统受信任的 CA 证书 | +| `mozilla` | [Mozilla 包含列表](https://wiki.mozilla.org/CA/Included_Certificates)(已移除中国 CA 证书) | +| `chrome` | [Chrome Root Store](https://g.co/chrome/root-policy)(已移除中国 CA 证书) | +| `none` | 空列表 | #### certificate From 35ff7d1fb46f30a25fabec3e74f15920f020fcbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 21 Dec 2025 23:01:42 +0800 Subject: [PATCH 064/185] Update quic-go to v0.58.0 --- go.mod | 26 ++- go.sum | 48 +++--- protocol/naive/quic/inbound_init.go | 27 ++- test/go.mod | 116 ++++++------- test/go.sum | 250 ++++++++++++++-------------- transport/v2rayquic/server.go | 2 +- transport/v2rayquic/stream.go | 8 +- 7 files changed, 240 insertions(+), 237 deletions(-) diff --git a/go.mod b/go.mod index 7a3727fa..de16ddac 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/go-chi/chi/v5 v5.2.2 github.com/go-chi/render v1.0.3 github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 - github.com/gofrs/uuid/v5 v5.3.2 + github.com/gofrs/uuid/v5 v5.4.0 github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f github.com/keybase/go-keychain v0.0.1 github.com/libdns/alidns v1.0.5-libdns.v1.beta1 @@ -31,10 +31,10 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 - github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 - github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 + github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 + github.com/sagernet/sing v0.8.0-beta.7 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 + github.com/sagernet/sing-quic v0.6.0-beta.7 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 @@ -49,19 +49,17 @@ require ( github.com/vishvananda/netns v0.0.5 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.42.0 + golang.org/x/crypto v0.46.0 golang.org/x/exp v0.0.0-20250911091902-df9299821621 - golang.org/x/mod v0.28.0 - golang.org/x/net v0.44.0 - golang.org/x/sys v0.36.0 + golang.org/x/mod v0.30.0 + golang.org/x/net v0.47.0 + golang.org/x/sys v0.39.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 howett.net/plist v1.0.1 ) -//replace github.com/sagernet/sing => ../sing - require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/ajg/form v1.5.1 // indirect @@ -151,11 +149,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/tools v0.39.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect diff --git a/go.sum b/go.sum index 5412118a..a57e2ca2 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= -github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= +github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -211,14 +211,14 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 h1:Rah/tDukrowqlznHQgXD4E9/yEsVsEMIxBZzS2NorGc= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.3/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= -github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 h1:E9yZrU0ZxSiW5RrGUnFZeI02EIMdAAv0RxdoxXCqZyk= +github.com/sagernet/quic-go v0.58.0-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/sing v0.8.0-beta.7 h1:RGM+r+Mrq7BVt3fLHJXgFzNWmtAfqgWaDkEkb4KUlBM= +github.com/sagernet/sing v0.8.0-beta.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 h1:YNXy008FFkVwfNjCR8GlVTlHbBXPwv8Y4ytDnSFUSDo= -github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0/go.mod h1:Y3YVjPutLHLQvYjGtUFH+w8YmM4MTd19NDzxLZGNGIs= +github.com/sagernet/sing-quic v0.6.0-beta.7 h1:Sh6KltQ6nB69S9ZdDKs5oARqkyY99gOaHe1JPxNV7ag= +github.com/sagernet/sing-quic v0.6.0-beta.7/go.mod h1:0NodMFjlAvfLp87Enpx46fQPrGRvmbUsmy5hRLzomtM= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= @@ -311,21 +311,21 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= 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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -333,20 +333,20 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/protocol/naive/quic/inbound_init.go b/protocol/naive/quic/inbound_init.go index 956132ac..a356cfae 100644 --- a/protocol/naive/quic/inbound_init.go +++ b/protocol/naive/quic/inbound_init.go @@ -4,12 +4,14 @@ import ( "context" "io" "net/http" + "time" "github.com/sagernet/quic-go" "github.com/sagernet/quic-go/congestion" "github.com/sagernet/quic-go/http3" "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-quic" @@ -35,11 +37,15 @@ func init() { } var congestionControl func(conn *quic.Conn) congestion.CongestionControl + timeFunc := ntp.TimeFuncFromContext(ctx) + if timeFunc == nil { + timeFunc = time.Now + } switch options.QUICCongestionControl { case "", "bbr": congestionControl = func(conn *quic.Conn) congestion.CongestionControl { return congestion_meta2.NewBbrSender( - congestion_meta2.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion_meta2.DefaultClock{TimeFunc: timeFunc}, congestion.ByteCount(conn.Config().InitialPacketSize), congestion.ByteCount(congestion_meta1.InitialCongestionWindow), ) @@ -47,7 +53,7 @@ func init() { case "bbr_standard": congestionControl = func(conn *quic.Conn) congestion.CongestionControl { return congestion_bbr1.NewBbrSender( - congestion_bbr1.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion_bbr1.DefaultClock{TimeFunc: timeFunc}, congestion.ByteCount(conn.Config().InitialPacketSize), congestion_bbr1.InitialCongestionWindowPackets, congestion_bbr1.MaxCongestionWindowPackets, @@ -56,7 +62,7 @@ func init() { case "bbr2": congestionControl = func(conn *quic.Conn) congestion.CongestionControl { return congestion_bbr2.NewBBR2Sender( - congestion_bbr2.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion_bbr2.DefaultClock{TimeFunc: timeFunc}, congestion.ByteCount(conn.Config().InitialPacketSize), 0, false, @@ -65,7 +71,7 @@ func init() { case "bbr2_variant": congestionControl = func(conn *quic.Conn) congestion.CongestionControl { return congestion_bbr2.NewBBR2Sender( - congestion_bbr2.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion_bbr2.DefaultClock{TimeFunc: timeFunc}, congestion.ByteCount(conn.Config().InitialPacketSize), 32*congestion.ByteCount(conn.Config().InitialPacketSize), true, @@ -74,7 +80,7 @@ func init() { case "cubic": congestionControl = func(conn *quic.Conn) congestion.CongestionControl { return congestion_meta1.NewCubicSender( - congestion_meta1.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion_meta1.DefaultClock{TimeFunc: timeFunc}, congestion.ByteCount(conn.Config().InitialPacketSize), false, ) @@ -82,7 +88,7 @@ func init() { case "reno": congestionControl = func(conn *quic.Conn) congestion.CongestionControl { return congestion_meta1.NewCubicSender( - congestion_meta1.DefaultClock{TimeFunc: ntp.TimeFuncFromContext(ctx)}, + congestion_meta1.DefaultClock{TimeFunc: timeFunc}, congestion.ByteCount(conn.Config().InitialPacketSize), true, ) @@ -92,9 +98,8 @@ func init() { } quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{ - MaxIncomingStreams: 1 << 60, - Allow0RTT: true, - GetCongestionControl: congestionControl, + MaxIncomingStreams: 1 << 60, + Allow0RTT: true, }) if err != nil { udpConn.Close() @@ -103,6 +108,10 @@ func init() { h3Server := &http3.Server{ Handler: handler, + ConnContext: func(ctx context.Context, conn *quic.Conn) context.Context { + conn.SetCongestionControl(congestionControl(conn)) + return log.ContextWithNewID(ctx) + }, } go func() { diff --git a/test/go.mod b/test/go.mod index 9953b46a..ee69f102 100644 --- a/test/go.mod +++ b/test/go.mod @@ -9,16 +9,16 @@ replace github.com/sagernet/sing-box => ../ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 - github.com/gofrs/uuid/v5 v5.3.2 - github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 + github.com/gofrs/uuid/v5 v5.4.0 + github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 - github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 + github.com/sagernet/sing-quic v0.6.0-beta.6 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/spyzhov/ajson v0.9.4 github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.44.0 + golang.org/x/net v0.48.0 ) require ( @@ -28,11 +28,11 @@ require ( github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/anthropics/anthropic-sdk-go v1.14.0 // indirect + github.com/anthropics/anthropic-sdk-go v1.19.0 // indirect github.com/anytls/sing-anytls v0.0.11 // indirect - github.com/caddyserver/certmagic v0.23.0 // indirect + github.com/caddyserver/certmagic v0.25.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect - github.com/coder/websocket v1.8.13 // indirect + github.com/coder/websocket v1.8.14 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/cretz/bine v0.2.0 // indirect @@ -48,15 +48,15 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gaissmai/bart v0.18.0 // indirect - github.com/go-chi/chi/v5 v5.2.2 // indirect + github.com/go-chi/chi/v5 v5.2.3 // indirect github.com/go-chi/render v1.0.3 // indirect github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect - github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect + github.com/godbus/dbus/v5 v5.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/btree v1.1.3 // indirect @@ -66,27 +66,27 @@ require ( github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/illarion/gonotify/v3 v3.0.2 // indirect - github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f // indirect + github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/keybase/go-keychain v0.0.1 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect - github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect - github.com/libdns/libdns v1.1.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/libdns/alidns v1.0.6-beta.3 // indirect + github.com/libdns/cloudflare v0.2.2 // indirect + github.com/libdns/libdns v1.1.1 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/metacubex/utls v1.8.3 // indirect - github.com/mholt/acmez/v3 v3.1.2 // indirect - github.com/miekg/dns v1.1.67 // indirect + github.com/mholt/acmez/v3 v3.1.4 // indirect + github.com/miekg/dns v1.1.69 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/openai/openai-go/v3 v3.13.0 // indirect + github.com/openai/openai-go/v3 v3.15.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect @@ -97,30 +97,30 @@ require ( github.com/safchain/ethtool v0.3.0 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cors v1.2.1 // indirect - github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f // indirect - github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 // indirect + github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a // indirect + github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect @@ -149,31 +149,31 @@ require ( github.com/vishvananda/netns v0.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect - golang.org/x/mod v0.28.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect - google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/grpc v1.77.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect diff --git a/test/go.sum b/test/go.sum index d7175af8..a7725af2 100644 --- a/test/go.sum +++ b/test/go.sum @@ -12,20 +12,20 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anthropics/anthropic-sdk-go v1.14.0 h1:EzNQvnZlaDHe2UPkoUySDz3ixRgNbwKdH8KtFpv7pi4= -github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE= +github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= +github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic= +github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= -github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= -github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= @@ -64,15 +64,15 @@ github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -81,10 +81,10 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= -github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk= +github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= +github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -107,8 +107,8 @@ github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= -github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU= -github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM= +github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU= +github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= @@ -117,19 +117,18 @@ 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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ= -github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= -github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8= -github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= -github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU= -github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8= +github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= +github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= +github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= +github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= @@ -142,10 +141,10 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= -github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= -github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= -github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= +github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= +github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -156,8 +155,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q= -github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= +github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo= +github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= 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= @@ -173,62 +172,62 @@ github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyf github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f h1:WTHyVtd9nNZ4VB20aja31e0ZXXGrVlssAanJJBMc5BU= -github.com/sagernet/cronet-go v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f h1:qupezSQEMraq2yajI4HrWf9h9rY7RESUYbYHRRd49FY= -github.com/sagernet/cronet-go/all v0.0.0-20251218041957-eeb28f1dba2f/go.mod h1:onaFo5hJh1KsvuxkYTFLokh0Yx2oBh9J3yvFTnFB1Fc= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:jmDdDxVFN/W+pX/QRHYR6jFu5gosRPNSSPemGmchHpM= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:zQeDQJg2YZSlmR5+mYV4KyqIWwe+SH5hnxTO4EalDwA= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:85ck9+7Ftj+J3RO1uusn1Y3EXRLX9Dy0WIK/ZjRlQok= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:D//nc8RxHp7tTKcIYYQZ80wQBwxlCJv6HpkrCkqIVRo= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:wwrQCcrwnHSY/Y4cy8JCiZLzuWXM8tENphhJtFplh0c= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:Q52ojQVXk9f6Z+EWHWv+SJajNF56bVvgMyQP/7WV2TI= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:UzXQ09sZQZDlsZvWd4SAhtaF0RMxAVg4TEYsRNMutjU= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:sjSknfRi3fMJqwHW4pNL64SGq7Y5XIm4pz5Ds21hvKw= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:uxb8pqzB4KH7ai6MocOWQPu3/+BAYISSIMPHWs0wtrQ= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7 h1:uV78fIQ2XdVQUzowiafKP2fs+rGlkI+9u7l1cBMlQlc= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:6d87Pvio5dpWakic3SNBVTwTlt8ZnvL1GWPPeY2xSLs= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:tc3GdN9pAfZqdfb55DmtUSXEuKuecWNYSD/+4jclXmo= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:W5fN1B1wYgnh7Y5Nw+MgcDjsvdPvEPAGe27iemq2LJs= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7 h1:xfNH8CO8p2W8rtVFgyv8kiAkzoSArW6+h2s/p5ZNvP8= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:1mkhgxLOh1pPjBLgC/GK3Rhhinz0254OjGJRrivVhQ8= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:zf252WwVhJYduTDBXvkBKdwSU+Ce0OToQzvy5SwPS3c= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7 h1:Nhkn2M2UsGe0hr6qOgNKvGKgAbo6EfWQto+YjlNWGqg= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:HSvWFyBDLC7grFuSwBSIaxLI/MVpe/w4qJJAWAZsBxc= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:PJa1fhmbXWXCYk07DZhqzjP2u9rM/cnS0A1aaPU56pY= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7 h1:Tpskq8p8boYHkBya18ythhgWTMv8gFsseGk55MFW+/k= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7 h1:qjB64lzTP0ROuk9GCCwsNQ9ca37nIr75iOyc2vuGFQg= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7 h1:GDJjygR/HLWOzwAs0RjhvqN6d1rUm7WiDAmHNe8gRAM= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251218041530-c2a431c5f1f7/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a h1:EdIrRa0yH3ZaLOfX95RYYiN0etpX3b9+jcsW/D8jCyQ= +github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a h1:GU/oBPVjyrCVb2l0SI5qtF6aUlLsnE4u4ehXbS/NF+0= +github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a/go.mod h1:4MT0juyKK0lIVwa6+6xLbRMFJBUIqRZOx70iMvq5vf0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e h1:EAcY0ZK8DWTNUdVCKdBQfXRsfGsohsh2ae9jIjlri2o= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:9luoNSy9Ej8rC/nZejzrP0uxX+BxiNxjLJ7XPYlApQg= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e h1:CSWZTuMlkO/98RSQpqC1EHtc9eSBzVmvMdPTnaFSDCM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:vkf3GDA11NtGm6XQHyP4tgWK3GD426ObmshdKdxGMhA= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:pNyNjDC5XPC6+2yNqiDA8QL8IYSsBJpaSLwPi/jY4JQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:2yDmTzppvxWuwxo4oDjxRzjlXBzZ6O3OCPWYeQcJRrw= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:+lyrRlxlKBYT/3LcYMaFHilUnRlKn3OQrImlWCey+qM= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:dBeeUaCPG0Y26FfCPO1o3v72yw2pSjqopryAW0uV354= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:JkGEqkfeglehN9tu+a0gHTq9Dmm+pY2wtjY+NiUdjgw= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e h1:rd/AbuuEMCHcA6ib5JBkQmWvonnoGwVc0/P0sHcWfAQ= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e h1:4XcwU6KOCkVOAr2EKKY8geYm7ctKCLlb1SsmFWMMUzE= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:fYDhPtxvMtZxBUM0k6GcaxbynLxMG2iTmQL3XcLtjS8= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:F5Vzd50H/AaoSHvt81yXZeUwDRKTcFjO/+KVW6VqbUs= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e h1:Ok/Eh8ajcvxlu7FAWk+lHAPLcSRDKrKkgq30o6gCR6M= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:JoohVAfcwE9HjpwAaTULtEOFZ1Mz0Zz5K4pOcrRjqwo= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:ULxfoxcNRA96QDDRLmObH4adbeUGtudH/2HlL4TgaNY= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e h1:NnhScXJyx4Fg+pV/WtkC1mD6+VR7D/Q+PMU4xGRvdRQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:INcIfjzUl0qgtZkt8OcVV537GoApcBsdegDl8yNJEdQ= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:jrialJ4U7BW9xyGI/JdUiigZ9hgyF1EayFKCH4pZxdw= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:XefHhnALVHU4ZgICNq6ZJl78bWDQO4nHwoWn+Ar7e0g= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:djShWO5vN0CamyboNDyNRDxL0/9oMtKjCDesqolIAsg= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:AgKqyS5zfRgDc8uprxUh+XCwwzJCj31KAFPjzyLRWs0= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= @@ -237,15 +236,13 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.3 h1:Rah/tDukrowqlznHQgXD4E9/yEsVsEMIxBZzS2NorGc= -github.com/sagernet/quic-go v0.57.1-sing-box-mod.3/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 h1:E9yZrU0ZxSiW5RrGUnFZeI02EIMdAAv0RxdoxXCqZyk= +github.com/sagernet/quic-go v0.58.0-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0 h1:YNXy008FFkVwfNjCR8GlVTlHbBXPwv8Y4ytDnSFUSDo= -github.com/sagernet/sing-quic v0.6.0-beta.5.0.20251218085114-6968f531a8c0/go.mod h1:Y3YVjPutLHLQvYjGtUFH+w8YmM4MTd19NDzxLZGNGIs= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= @@ -322,32 +319,32 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= 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.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= @@ -358,30 +355,30 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 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= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -389,28 +386,27 @@ golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= 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= @@ -419,14 +415,16 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/transport/v2rayquic/server.go b/transport/v2rayquic/server.go index bde6e87a..c50d92f0 100644 --- a/transport/v2rayquic/server.go +++ b/transport/v2rayquic/server.go @@ -88,7 +88,7 @@ func (s *Server) streamAcceptLoop(conn *quic.Conn) error { for { stream, err := conn.AcceptStream(s.ctx) if err != nil { - return err + return qtls.WrapError(err) } go s.handler.NewConnectionEx(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.SocksaddrFromNet(conn.RemoteAddr()), M.Socksaddr{}, nil) } diff --git a/transport/v2rayquic/stream.go b/transport/v2rayquic/stream.go index 5ec3b7cb..aad62afb 100644 --- a/transport/v2rayquic/stream.go +++ b/transport/v2rayquic/stream.go @@ -4,7 +4,7 @@ import ( "net" "github.com/sagernet/quic-go" - "github.com/sagernet/sing/common/baderror" + qtls "github.com/sagernet/sing-quic" ) type StreamWrapper struct { @@ -14,14 +14,12 @@ type StreamWrapper struct { func (s *StreamWrapper) Read(p []byte) (n int, err error) { n, err = s.Stream.Read(p) - //nolint:staticcheck - return n, baderror.WrapQUIC(err) + return n, qtls.WrapError(err) } func (s *StreamWrapper) Write(p []byte) (n int, err error) { n, err = s.Stream.Write(p) - //nolint:staticcheck - return n, baderror.WrapQUIC(err) + return n, qtls.WrapError(err) } func (s *StreamWrapper) LocalAddr() net.Addr { From ddec2ab28254e834a6bd422465111b3431b64285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 21 Dec 2025 23:11:00 +0800 Subject: [PATCH 065/185] Update dependencies --- common/tls/acme.go | 12 +- docs/configuration/shared/dns01_challenge.md | 33 ++++- .../shared/dns01_challenge.zh.md | 33 ++++- go.mod | 46 +++---- go.sum | 125 +++++++++--------- option/tls_acme.go | 4 +- 6 files changed, 155 insertions(+), 98 deletions(-) diff --git a/common/tls/acme.go b/common/tls/acme.go index 4a79c56c..b8aef70f 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -114,13 +114,17 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound switch dnsOptions.Provider { case C.DNSProviderAliDNS: solver.DNSProvider = &alidns.Provider{ - AccKeyID: dnsOptions.AliDNSOptions.AccessKeyID, - AccKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret, - RegionID: dnsOptions.AliDNSOptions.RegionID, + CredentialInfo: alidns.CredentialInfo{ + AccessKeyID: dnsOptions.AliDNSOptions.AccessKeyID, + AccessKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret, + RegionID: dnsOptions.AliDNSOptions.RegionID, + SecurityToken: dnsOptions.AliDNSOptions.SecurityToken, + }, } case C.DNSProviderCloudflare: solver.DNSProvider = &cloudflare.Provider{ - APIToken: dnsOptions.CloudflareOptions.APIToken, + APIToken: dnsOptions.CloudflareOptions.APIToken, + ZoneToken: dnsOptions.CloudflareOptions.ZoneToken, } default: return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider) diff --git a/docs/configuration/shared/dns01_challenge.md b/docs/configuration/shared/dns01_challenge.md index f9949e16..23265bd6 100644 --- a/docs/configuration/shared/dns01_challenge.md +++ b/docs/configuration/shared/dns01_challenge.md @@ -1,9 +1,18 @@ +--- +icon: material/new-box +--- + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [alidns.security_token](#security_token) + :material-plus: [cloudflare.zone_token](#zone_token) + ### Structure ```json { "provider": "", - + ... // Provider Fields } ``` @@ -17,15 +26,31 @@ "provider": "alidns", "access_key_id": "", "access_key_secret": "", - "region_id": "" + "region_id": "", + "security_token": "" } ``` +##### security_token + +!!! question "Since sing-box 1.13.0" + +The Security Token for STS temporary credentials. + #### Cloudflare ```json { "provider": "cloudflare", - "api_token": "" + "api_token": "", + "zone_token": "" } -``` \ No newline at end of file +``` + +##### zone_token + +!!! question "Since sing-box 1.13.0" + +Optional API token with `Zone:Read` permission. + +When provided, allows `api_token` to be scoped to a single zone. diff --git a/docs/configuration/shared/dns01_challenge.zh.md b/docs/configuration/shared/dns01_challenge.zh.md index c942fef0..c316a9fd 100644 --- a/docs/configuration/shared/dns01_challenge.zh.md +++ b/docs/configuration/shared/dns01_challenge.zh.md @@ -1,9 +1,18 @@ +--- +icon: material/new-box +--- + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [alidns.security_token](#security_token) + :material-plus: [cloudflare.zone_token](#zone_token) + ### 结构 ```json { "provider": "", - + ... // 提供商字段 } ``` @@ -17,15 +26,31 @@ "provider": "alidns", "access_key_id": "", "access_key_secret": "", - "region_id": "" + "region_id": "", + "security_token": "" } ``` +##### security_token + +!!! question "自 sing-box 1.13.0 起" + +用于 STS 临时凭证的安全令牌。 + #### Cloudflare ```json { "provider": "cloudflare", - "api_token": "" + "api_token": "", + "zone_token": "" } -``` \ No newline at end of file +``` + +##### zone_token + +!!! question "自 sing-box 1.13.0 起" + +具有 `Zone:Read` 权限的可选 API 令牌。 + +提供后可将 `api_token` 限定到单个区域。 diff --git a/go.mod b/go.mod index de16ddac..b03a8447 100644 --- a/go.mod +++ b/go.mod @@ -3,25 +3,25 @@ module github.com/sagernet/sing-box go 1.24.7 require ( - github.com/anthropics/anthropic-sdk-go v1.14.0 + github.com/anthropics/anthropic-sdk-go v1.19.0 github.com/anytls/sing-anytls v0.0.11 - github.com/caddyserver/certmagic v0.23.0 - github.com/coder/websocket v1.8.13 + github.com/caddyserver/certmagic v0.25.0 + github.com/coder/websocket v1.8.14 github.com/cretz/bine v0.2.0 github.com/database64128/tfo-go/v2 v2.3.1 - github.com/go-chi/chi/v5 v5.2.2 + github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/render v1.0.3 - github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 + github.com/godbus/dbus/v5 v5.2.1 github.com/gofrs/uuid/v5 v5.4.0 - github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f + github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 github.com/keybase/go-keychain v0.0.1 - github.com/libdns/alidns v1.0.5-libdns.v1.beta1 - github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 + github.com/libdns/alidns v1.0.6-beta.3 + github.com/libdns/cloudflare v0.2.2 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/utls v1.8.4 - github.com/mholt/acmez/v3 v3.1.2 - github.com/miekg/dns v1.1.67 - github.com/openai/openai-go/v3 v3.13.0 + github.com/mholt/acmez/v3 v3.1.4 + github.com/miekg/dns v1.1.69 + github.com/openai/openai-go/v3 v3.15.0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a @@ -44,19 +44,19 @@ require ( github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 - github.com/spf13/cobra v1.9.1 + github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 github.com/vishvananda/netns v0.0.5 - go.uber.org/zap v1.27.0 + go.uber.org/zap v1.27.1 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.46.0 - golang.org/x/exp v0.0.0-20250911091902-df9299821621 - golang.org/x/mod v0.30.0 - golang.org/x/net v0.47.0 + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 + golang.org/x/mod v0.31.0 + golang.org/x/net v0.48.0 golang.org/x/sys v0.39.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 - google.golang.org/grpc v1.73.0 - google.golang.org/protobuf v1.36.6 + google.golang.org/grpc v1.77.0 + google.golang.org/protobuf v1.36.11 howett.net/plist v1.0.1 ) @@ -94,8 +94,8 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/libdns/libdns v1.1.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/libdns/libdns v1.1.1 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect @@ -130,7 +130,7 @@ require ( github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect @@ -153,10 +153,10 @@ require ( golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index a57e2ca2..e5f88639 100644 --- a/go.sum +++ b/go.sum @@ -8,20 +8,20 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anthropics/anthropic-sdk-go v1.14.0 h1:EzNQvnZlaDHe2UPkoUySDz3ixRgNbwKdH8KtFpv7pi4= -github.com/anthropics/anthropic-sdk-go v1.14.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE= +github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= +github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic= +github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= -github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= -github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -50,14 +50,14 @@ github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -66,8 +66,8 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= -github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= +github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk= +github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -93,8 +93,8 @@ github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeV github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= 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-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU= -github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM= +github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU= +github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= @@ -102,15 +102,14 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ= -github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g= -github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8= -github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= -github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU= -github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8= +github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= +github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= +github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= +github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= +github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= @@ -123,16 +122,16 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= -github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= -github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= -github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= +github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= +github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q= -github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= +github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo= +github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -237,10 +236,10 @@ github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1: github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -286,26 +285,27 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= 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.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= @@ -313,16 +313,16 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/W golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= 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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= @@ -330,7 +330,6 @@ golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= @@ -345,8 +344,8 @@ golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -356,12 +355,14 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdI golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= diff --git a/option/tls_acme.go b/option/tls_acme.go index 50270607..1857c747 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -75,8 +75,10 @@ type ACMEDNS01AliDNSOptions struct { AccessKeyID string `json:"access_key_id,omitempty"` AccessKeySecret string `json:"access_key_secret,omitempty"` RegionID string `json:"region_id,omitempty"` + SecurityToken string `json:"security_token,omitempty"` } type ACMEDNS01CloudflareOptions struct { - APIToken string `json:"api_token,omitempty"` + APIToken string `json:"api_token,omitempty"` + ZoneToken string `json:"zone_token,omitempty"` } From c2b697a778fd5a48c6ac876e0ea8c8d8af504cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Dec 2025 02:35:24 +0800 Subject: [PATCH 066/185] Fix missing build constraints for linux wifi state monitor --- common/settings/wifi_linux_connman.go | 2 ++ common/settings/wifi_linux_iwd.go | 2 ++ common/settings/wifi_linux_nm.go | 2 ++ 3 files changed, 6 insertions(+) diff --git a/common/settings/wifi_linux_connman.go b/common/settings/wifi_linux_connman.go index 130636bc..74706a7b 100644 --- a/common/settings/wifi_linux_connman.go +++ b/common/settings/wifi_linux_connman.go @@ -1,3 +1,5 @@ +//go:build linux + package settings import ( diff --git a/common/settings/wifi_linux_iwd.go b/common/settings/wifi_linux_iwd.go index 22dfe720..327f9c47 100644 --- a/common/settings/wifi_linux_iwd.go +++ b/common/settings/wifi_linux_iwd.go @@ -1,3 +1,5 @@ +//go:build linux + package settings import ( diff --git a/common/settings/wifi_linux_nm.go b/common/settings/wifi_linux_nm.go index 4288d210..77d897d4 100644 --- a/common/settings/wifi_linux_nm.go +++ b/common/settings/wifi_linux_nm.go @@ -1,3 +1,5 @@ +//go:build linux + package settings import ( From 203f4134b019f13e105c96c16abdd18edb5dab04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Dec 2025 04:51:27 +0800 Subject: [PATCH 067/185] documentation: Add Wi-Fi state shared page --- docs/configuration/route/rule.md | 12 +++---- docs/configuration/route/rule.zh.md | 12 +++---- docs/configuration/shared/wifi-state.md | 41 ++++++++++++++++++++++ docs/configuration/shared/wifi-state.zh.md | 41 ++++++++++++++++++++++ mkdocs.yml | 2 ++ 5 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 docs/configuration/shared/wifi-state.md create mode 100644 docs/configuration/shared/wifi-state.zh.md diff --git a/docs/configuration/route/rule.md b/docs/configuration/route/rule.md index 6bbb00de..31f768fe 100644 --- a/docs/configuration/route/rule.md +++ b/docs/configuration/route/rule.md @@ -428,20 +428,16 @@ Match default interface address. #### wifi_ssid -!!! quote "" - - Only supported in graphical clients on Android and Apple platforms, or on Linux. - Match WiFi SSID. +See [Wi-Fi State](/configuration/shared/wifi-state/) for details. + #### wifi_bssid -!!! quote "" - - Only supported in graphical clients on Android and Apple platforms, or on Linux. - Match WiFi BSSID. +See [Wi-Fi State](/configuration/shared/wifi-state/) for details. + #### preferred_by !!! question "Since sing-box 1.13.0" diff --git a/docs/configuration/route/rule.zh.md b/docs/configuration/route/rule.zh.md index 164bf40e..1ffe57d6 100644 --- a/docs/configuration/route/rule.zh.md +++ b/docs/configuration/route/rule.zh.md @@ -425,20 +425,16 @@ icon: material/new-box #### wifi_ssid -!!! quote "" - - 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 - 匹配 WiFi SSID。 +参阅 [Wi-Fi 状态](/zh/configuration/shared/wifi-state/)。 + #### wifi_bssid -!!! quote "" - - 仅在 Android 与 Apple 平台图形客户端和 Linux 中支持。 - 匹配 WiFi BSSID。 +参阅 [Wi-Fi 状态](/zh/configuration/shared/wifi-state/)。 + #### preferred_by !!! question "自 sing-box 1.13.0 起" diff --git a/docs/configuration/shared/wifi-state.md b/docs/configuration/shared/wifi-state.md new file mode 100644 index 00000000..b509d0e9 --- /dev/null +++ b/docs/configuration/shared/wifi-state.md @@ -0,0 +1,41 @@ +--- +icon: material/new-box +--- + +# Wi-Fi State + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: Linux support + :material-plus: Windows support + +sing-box can monitor Wi-Fi state to enable routing rules based on `wifi_ssid` and `wifi_bssid`. + +### Platform Support + +| Platform | Support | Notes | +|-----------------|------------------|--------------------------| +| Android | :material-check: | In graphical client | +| Apple platforms | :material-check: | In graphical clients | +| Linux | :material-check: | Requires supported daemon | +| Windows | :material-check: | WLAN API | +| Others | :material-close: | | + +### Linux + +!!! question "Since sing-box 1.13.0" + +The following backends are supported and will be auto-detected in order of priority: + +| Backend | Interface | +|------------------|-------------| +| NetworkManager | D-Bus | +| IWD | D-Bus | +| wpa_supplicant | Unix socket | +| ConnMan | D-Bus | + +### Windows + +!!! question "Since sing-box 1.13.0" + +Uses Windows WLAN API. diff --git a/docs/configuration/shared/wifi-state.zh.md b/docs/configuration/shared/wifi-state.zh.md new file mode 100644 index 00000000..a44340cf --- /dev/null +++ b/docs/configuration/shared/wifi-state.zh.md @@ -0,0 +1,41 @@ +--- +icon: material/new-box +--- + +# Wi-Fi 状态 + +!!! quote "sing-box 1.13.0 的变更" + + :material-plus: Linux 支持 + :material-plus: Windows 支持 + +sing-box 可以监控 Wi-Fi 状态,以启用基于 `wifi_ssid` 和 `wifi_bssid` 的路由规则。 + +### 平台支持 + +| 平台 | 支持 | 备注 | +|-----------------|------------------|----------------| +| Android | :material-check: | 仅图形客户端 | +| Apple 平台 | :material-check: | 仅图形客户端 | +| Linux | :material-check: | 需要支持的守护进程 | +| Windows | :material-check: | WLAN API | +| 其他 | :material-close: | | + +### Linux + +!!! question "自 sing-box 1.13.0 起" + +支持以下后端,将按优先级顺序自动探测: + +| 后端 | 接口 | +|------------------|-------------| +| NetworkManager | D-Bus | +| IWD | D-Bus | +| wpa_supplicant | Unix socket | +| ConnMan | D-Bus | + +### Windows + +!!! question "自 sing-box 1.13.0 起" + +使用 Windows WLAN API。 diff --git a/mkdocs.yml b/mkdocs.yml index a505f3e4..236252b9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -126,6 +126,7 @@ nav: - V2Ray Transport: configuration/shared/v2ray-transport.md - UDP over TCP: configuration/shared/udp-over-tcp.md - TCP Brutal: configuration/shared/tcp-brutal.md + - Wi-Fi State: configuration/shared/wifi-state.md - Endpoint: - configuration/endpoint/index.md - WireGuard: configuration/endpoint/wireguard.md @@ -268,6 +269,7 @@ plugins: DNS01 Challenge Fields: DNS01 验证字段 Multiplex: 多路复用 V2Ray Transport: V2Ray 传输层 + Wi-Fi State: Wi-Fi 状态 Endpoint: 端点 Inbound: 入站 From e0a78fde074bb59a66300783a9a78ce909a87e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Dec 2025 05:28:10 +0800 Subject: [PATCH 068/185] documentation: Minor fixes --- docs/configuration/dns/server/legacy.zh.md | 2 +- docs/configuration/inbound/shadowsocks.zh.md | 4 ++-- docs/configuration/outbound/tuic.zh.md | 2 +- docs/configuration/route/index.zh.md | 2 +- docs/configuration/shared/dial.md | 4 ++-- docs/configuration/shared/dial.zh.md | 4 ++-- docs/configuration/shared/listen.md | 2 +- docs/configuration/shared/listen.zh.md | 4 ++-- docs/configuration/shared/tls.zh.md | 2 +- docs/deprecated.zh.md | 4 ++-- docs/migration.zh.md | 4 ++-- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/configuration/dns/server/legacy.zh.md b/docs/configuration/dns/server/legacy.zh.md index 4bf2bcd3..4365749e 100644 --- a/docs/configuration/dns/server/legacy.zh.md +++ b/docs/configuration/dns/server/legacy.zh.md @@ -53,7 +53,7 @@ DNS 服务器的地址。 | `HTTP3` | `h3://8.8.8.8/dns-query` | | `RCode` | `rcode://refused` | | `DHCP` | `dhcp://auto` 或 `dhcp://en0` | -| [FakeIP](/configuration/dns/fakeip/) | `fakeip` | +| [FakeIP](/zh/configuration/dns/fakeip/) | `fakeip` | !!! warning "" diff --git a/docs/configuration/inbound/shadowsocks.zh.md b/docs/configuration/inbound/shadowsocks.zh.md index 55e0c481..c97e9bef 100644 --- a/docs/configuration/inbound/shadowsocks.zh.md +++ b/docs/configuration/inbound/shadowsocks.zh.md @@ -49,9 +49,9 @@ } ``` -### Listen Fields +### 监听字段 -See [Listen Fields](/configuration/shared/listen/) for details. +参阅 [监听字段](/zh/configuration/shared/listen/)。 ### 字段 diff --git a/docs/configuration/outbound/tuic.zh.md b/docs/configuration/outbound/tuic.zh.md index ee8fd15d..4511711a 100644 --- a/docs/configuration/outbound/tuic.zh.md +++ b/docs/configuration/outbound/tuic.zh.md @@ -66,7 +66,7 @@ UDP 包中继模式 #### udp_over_stream -这是 TUIC 的 [UDP over TCP 协议](/configuration/shared/udp-over-tcp/) 移植, 旨在提供 TUIC 不提供的 基于 QUIC 流的 UDP 中继模式。 由于它是一个附加协议,因此您需要使用 sing-box 或其他兼容的程序作为服务器。 +这是 TUIC 的 [UDP over TCP 协议](/zh/configuration/shared/udp-over-tcp/) 移植, 旨在提供 TUIC 不提供的 基于 QUIC 流的 UDP 中继模式。 由于它是一个附加协议,因此您需要使用 sing-box 或其他兼容的程序作为服务器。 此模式在正确的 UDP 代理场景中没有任何积极作用,仅适用于中继流式 UDP 流量(基本上是 QUIC 流)。 diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index 3748a522..fa50bfe7 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -62,7 +62,7 @@ icon: material/alert-decagram !!! question "自 sing-box 1.8.0 起" -一组 [规则集](/configuration/rule-set/)。 +一组 [规则集](/zh/configuration/rule-set/)。 #### final diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index cf775b65..634b951c 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -133,7 +133,7 @@ Disable TCP keep alive. Default value changed from `10m` to `5m`. -TCP keep-alive initial period. +TCP keep alive initial period. `5m` will be used by default. @@ -141,7 +141,7 @@ TCP keep-alive initial period. !!! question "Since sing-box 1.13.0" -TCP keep-alive interval. +TCP keep alive interval. `75s` will be used by default. diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index acd62213..9778585b 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -131,7 +131,7 @@ icon: material/new-box 默认值从 `10m` 更改为 `5m`。 -TCP keep-alive 初始周期。 +TCP keep alive 初始周期。 默认使用 `5m`。 @@ -139,7 +139,7 @@ TCP keep-alive 初始周期。 !!! question "自 sing-box 1.13.0 起" -TCP keep-alive 间隔。 +TCP keep alive 间隔。 默认使用 `75s`。 diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index 38a3749c..5da32ff0 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -127,7 +127,7 @@ TCP keep alive initial period. #### tcp_keep_alive_interval -TCP keep-alive interval. +TCP keep alive interval. `75s` will be used by default. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 8893f37b..90cf6a79 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -7,7 +7,7 @@ icon: material/new-box :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) :material-alert: [tcp_keep_alive](#tcp_keep_alive) -!!! quote "Changes in sing-box 1.12.0" +!!! quote "sing-box 1.12.0 中的更改" :material-plus: [netns](#netns) :material-plus: [bind_interface](#bind_interface) @@ -127,7 +127,7 @@ TCP keep alive 初始周期。 #### tcp_keep_alive_interval -TCP keep-alive 间隔。 +TCP keep alive 间隔。 默认使用 `75s`。 diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index 4d1b6a75..faf6d49a 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -627,7 +627,7 @@ MAC 密钥。 ACME DNS01 验证字段。如果配置,将禁用其他验证方法。 -参阅 [DNS01 验证字段](/configuration/shared/dns01_challenge/)。 +参阅 [DNS01 验证字段](/zh/configuration/shared/dns01_challenge/)。 ### Reality 字段 diff --git a/docs/deprecated.zh.md b/docs/deprecated.zh.md index c090432b..78c46053 100644 --- a/docs/deprecated.zh.md +++ b/docs/deprecated.zh.md @@ -95,7 +95,7 @@ GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。 maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过, 且现有的实现均存在内存使用大与管理困难的问题。 -sing-box 1.8.0 引入了[规则集](/configuration/rule-set/), +sing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/), 可以完全替代 GeoIP, 参阅 [迁移指南](/zh/migration/#geoip)。 #### Geosite @@ -105,7 +105,7 @@ Geosite 已废弃且将在 sing-box 1.12.0 中被移除。 Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案, 存在着包括缺少维护、规则不准确和管理困难内的大量问题。 -sing-box 1.8.0 引入了[规则集](/configuration/rule-set/), +sing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/), 可以完全替代 Geosite,参阅 [迁移指南](/zh/migration/#geosite)。 ## 1.6.0 diff --git a/docs/migration.zh.md b/docs/migration.zh.md index 3a4dde7f..6f8ba62a 100644 --- a/docs/migration.zh.md +++ b/docs/migration.zh.md @@ -10,8 +10,8 @@ DNS 服务器已经重构。 !!! info "引用" - [DNS 服务器](/configuration/dns/server/) / - [旧 DNS 服务器](/configuration/dns/server/legacy/) + [DNS 服务器](/zh/configuration/dns/server/) / + [旧 DNS 服务器](/zh/configuration/dns/server/legacy/) === "Local" From b2d90b7d8683af6d1cd894284652e00ac970eda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Dec 2025 13:37:36 +0800 Subject: [PATCH 069/185] Fix missing RootPoolFromContext and TimeFuncFromContext in HTTP clients --- service/ccm/service.go | 6 ++++++ service/ocm/service.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/service/ccm/service.go b/service/ccm/service.go index 84074694..94e47734 100644 --- a/service/ccm/service.go +++ b/service/ccm/service.go @@ -3,6 +3,7 @@ package ccm import ( "bytes" "context" + stdTLS "crypto/tls" "encoding/json" "errors" "io" @@ -26,6 +27,7 @@ import ( 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/common/ntp" aTLS "github.com/sagernet/sing/common/tls" "github.com/anthropics/anthropic-sdk-go" @@ -111,6 +113,10 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio httpClient := &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, + TLSClientConfig: &stdTLS.Config{ + RootCAs: adapter.RootPoolFromContext(ctx), + Time: ntp.TimeFuncFromContext(ctx), + }, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, diff --git a/service/ocm/service.go b/service/ocm/service.go index e8f95410..fc655f67 100644 --- a/service/ocm/service.go +++ b/service/ocm/service.go @@ -3,6 +3,7 @@ package ocm import ( "bytes" "context" + stdTLS "crypto/tls" "encoding/json" "errors" "io" @@ -26,6 +27,7 @@ import ( 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/common/ntp" aTLS "github.com/sagernet/sing/common/tls" "github.com/go-chi/chi/v5" @@ -103,6 +105,10 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio httpClient := &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, + TLSClientConfig: &stdTLS.Config{ + RootCAs: adapter.RootPoolFromContext(ctx), + Time: ntp.TimeFuncFromContext(ctx), + }, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) }, From f5ccf746eae2842832013b260bcc5e8a15c3f055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Dec 2025 17:17:21 +0800 Subject: [PATCH 070/185] platform: Split library for Android SDK 21 and 23 --- .github/workflows/build.yml | 4 +- cmd/internal/build_libbox/main.go | 83 ++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7614ee16..87f9e311 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -623,7 +623,7 @@ jobs: - name: Build run: |- mkdir clients/android/app/libs - cp libbox.aar clients/android/app/libs + cp *.aar clients/android/app/libs cd clients/android ./gradlew :app:assemblePlayRelease :app:assembleOtherRelease env: @@ -705,7 +705,7 @@ jobs: run: |- go run -v ./cmd/internal/update_android_version --ci mkdir clients/android/app/libs - cp libbox.aar clients/android/app/libs + cp *.aar clients/android/app/libs cd clients/android echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json ./gradlew :app:publishPlayReleaseBundle diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index f76ea492..6a0ccd08 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" _ "github.com/sagernet/gomobile" @@ -69,9 +70,27 @@ func init() { debugTags = append(debugTags, "debug") } -func buildAndroid() { - build_shared.FindSDK() +type AndroidBuildConfig struct { + AndroidAPI int + OutputName string + Tags []string +} +func filterTags(tags []string, exclude ...string) []string { + excludeMap := make(map[string]bool) + for _, tag := range exclude { + excludeMap[tag] = true + } + var result []string + for _, tag := range tags { + if !excludeMap[tag] { + result = append(result, tag) + } + } + return result +} + +func checkJavaVersion() { var javaPath string javaHome := os.Getenv("JAVA_HOME") if javaHome == "" { @@ -87,21 +106,24 @@ func buildAndroid() { if !strings.Contains(javaVersion, "openjdk 17") { log.Fatal("java version should be openjdk 17") } +} - var bindTarget string +func getAndroidBindTarget() string { if platform != "" { - bindTarget = platform + return platform } else if debugEnabled { - bindTarget = "android/arm64" - } else { - bindTarget = "android" + return "android/arm64" } + return "android" +} +func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) { args := []string{ "bind", "-v", + "-o", config.OutputName, "-target", bindTarget, - "-androidapi", "21", + "-androidapi", strconv.Itoa(config.AndroidAPI), "-javapkg=io.nekohasekai", "-libname=box", } @@ -112,34 +134,59 @@ func buildAndroid() { args = append(args, debugFlags...) } - tags := append(sharedTags, memcTags...) - if debugEnabled { - tags = append(tags, debugTags...) - } - - args = append(args, "-tags", strings.Join(tags, ",")) + args = append(args, "-tags", strings.Join(config.Tags, ",")) args = append(args, "./experimental/libbox") command := exec.Command(build_shared.GoBinPath+"/gomobile", args...) command.Stdout = os.Stdout command.Stderr = os.Stderr - err = command.Run() + err := command.Run() if err != nil { log.Fatal(err) } - const name = "libbox.aar" copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs") if rw.IsDir(copyPath) { copyPath, _ = filepath.Abs(copyPath) - err = rw.CopyFile(name, filepath.Join(copyPath, name)) + err = rw.CopyFile(config.OutputName, filepath.Join(copyPath, config.OutputName)) if err != nil { log.Fatal(err) } - log.Info("copied to ", copyPath) + log.Info("copied ", config.OutputName, " to ", copyPath) } } +func buildAndroid() { + build_shared.FindSDK() + checkJavaVersion() + + bindTarget := getAndroidBindTarget() + + // Build main variant (SDK 23) + mainTags := append([]string{}, sharedTags...) + mainTags = append(mainTags, memcTags...) + if debugEnabled { + mainTags = append(mainTags, debugTags...) + } + buildAndroidVariant(AndroidBuildConfig{ + AndroidAPI: 23, + OutputName: "libbox.aar", + Tags: mainTags, + }, bindTarget) + + // Build legacy variant (SDK 21, no naive outbound) + legacyTags := filterTags(sharedTags, "with_naive_outbound") + legacyTags = append(legacyTags, memcTags...) + if debugEnabled { + legacyTags = append(legacyTags, debugTags...) + } + buildAndroidVariant(AndroidBuildConfig{ + AndroidAPI: 21, + OutputName: "libbox-legacy.aar", + Tags: legacyTags, + }, bindTarget) +} + func buildApple() { var bindTarget string if platform != "" { From 4273ffa77ef234acb15d3fb9d302aab7fd885c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 25 Dec 2025 15:03:22 +0800 Subject: [PATCH 071/185] Update cronet-go to v143.0.7499.109-1 --- .github/CRONET_GO_VERSION | 2 +- .github/workflows/build.yml | 11 +++-- go.mod | 48 +++++++++---------- go.sum | 96 ++++++++++++++++++------------------- protocol/naive/outbound.go | 2 +- 5 files changed, 80 insertions(+), 79 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index f887e9a2..c320cf1a 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -6228b14f72eea9db301d1fbe771c7bdecadefb40 +b0385d27c2ab659d9532d71f301deb6599c44a79 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87f9e311..72f33fe5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,10 +102,10 @@ jobs: - { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" } - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" } - - { os: android, arch: arm64, ndk: "aarch64-linux-android21" } - - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" } - - { os: android, arch: amd64, ndk: "x86_64-linux-android21" } - - { os: android, arch: "386", ndk: "i686-linux-android21" } + - { os: android, arch: arm64, ndk: "aarch64-linux-android23" } + - { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" } + - { os: android, arch: amd64, ndk: "x86_64-linux-android23" } + - { os: android, arch: "386", ndk: "i686-linux-android23" } steps: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 @@ -625,7 +625,7 @@ jobs: mkdir clients/android/app/libs cp *.aar clients/android/app/libs cd clients/android - ./gradlew :app:assemblePlayRelease :app:assembleOtherRelease + ./gradlew :app:assembleOtherRelease :app:assembleOtherLegacyRelease env: JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} @@ -635,6 +635,7 @@ jobs: mkdir -p dist #cp clients/android/app/build/outputs/apk/play/release/*.apk dist cp clients/android/app/build/outputs/apk/other/release/*.apk dist + cp clients/android/app/build/outputs/apk/otherLegacy/release/*.apk dist VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2) VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2) cat > dist/SFA-version-metadata.json << EOF diff --git a/go.mod b/go.mod index b03a8447..13d1082d 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a - github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a + github.com/sagernet/cronet-go v0.0.0-20251222042025-2235d8db9f7f + github.com/sagernet/cronet-go/all v0.0.0-20251222042025-2235d8db9f7f github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -106,28 +106,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index e5f88639..0c4235b0 100644 --- a/go.sum +++ b/go.sum @@ -152,54 +152,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a h1:EdIrRa0yH3ZaLOfX95RYYiN0etpX3b9+jcsW/D8jCyQ= -github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a h1:GU/oBPVjyrCVb2l0SI5qtF6aUlLsnE4u4ehXbS/NF+0= -github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a/go.mod h1:4MT0juyKK0lIVwa6+6xLbRMFJBUIqRZOx70iMvq5vf0= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e h1:EAcY0ZK8DWTNUdVCKdBQfXRsfGsohsh2ae9jIjlri2o= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:9luoNSy9Ej8rC/nZejzrP0uxX+BxiNxjLJ7XPYlApQg= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e h1:CSWZTuMlkO/98RSQpqC1EHtc9eSBzVmvMdPTnaFSDCM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:vkf3GDA11NtGm6XQHyP4tgWK3GD426ObmshdKdxGMhA= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:pNyNjDC5XPC6+2yNqiDA8QL8IYSsBJpaSLwPi/jY4JQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:2yDmTzppvxWuwxo4oDjxRzjlXBzZ6O3OCPWYeQcJRrw= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:+lyrRlxlKBYT/3LcYMaFHilUnRlKn3OQrImlWCey+qM= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:dBeeUaCPG0Y26FfCPO1o3v72yw2pSjqopryAW0uV354= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:JkGEqkfeglehN9tu+a0gHTq9Dmm+pY2wtjY+NiUdjgw= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e h1:rd/AbuuEMCHcA6ib5JBkQmWvonnoGwVc0/P0sHcWfAQ= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e h1:4XcwU6KOCkVOAr2EKKY8geYm7ctKCLlb1SsmFWMMUzE= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:fYDhPtxvMtZxBUM0k6GcaxbynLxMG2iTmQL3XcLtjS8= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:F5Vzd50H/AaoSHvt81yXZeUwDRKTcFjO/+KVW6VqbUs= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e h1:Ok/Eh8ajcvxlu7FAWk+lHAPLcSRDKrKkgq30o6gCR6M= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:JoohVAfcwE9HjpwAaTULtEOFZ1Mz0Zz5K4pOcrRjqwo= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:ULxfoxcNRA96QDDRLmObH4adbeUGtudH/2HlL4TgaNY= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e h1:NnhScXJyx4Fg+pV/WtkC1mD6+VR7D/Q+PMU4xGRvdRQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:INcIfjzUl0qgtZkt8OcVV537GoApcBsdegDl8yNJEdQ= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:jrialJ4U7BW9xyGI/JdUiigZ9hgyF1EayFKCH4pZxdw= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:XefHhnALVHU4ZgICNq6ZJl78bWDQO4nHwoWn+Ar7e0g= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:djShWO5vN0CamyboNDyNRDxL0/9oMtKjCDesqolIAsg= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:AgKqyS5zfRgDc8uprxUh+XCwwzJCj31KAFPjzyLRWs0= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251222042025-2235d8db9f7f h1:F8MAWpgYSZp9xAuvmiqRRF2fydn49qbao3pFduuClYw= +github.com/sagernet/cronet-go v0.0.0-20251222042025-2235d8db9f7f/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251222042025-2235d8db9f7f h1:oxWJoIIF19Zcp438OTQLtXhFHcYzjw+QRjwhhOzRRrQ= +github.com/sagernet/cronet-go/all v0.0.0-20251222042025-2235d8db9f7f/go.mod h1:TTaiEGVltL4Dzde1pL5f8hxbsNhOZmsEBEUC0yqGiyw= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251222041542-7fa506facd17 h1:Mi/DrxIKK20bkvr8qV6Fxa/dc6yoRpyFkqe/HcVwxYE= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251222041542-7fa506facd17/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251222041542-7fa506facd17 h1:T07c63qkYlZCs2nDZL4Tt2FJQFKWoj6GThlw3Osq3PY= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251222041542-7fa506facd17 h1:wDect54Q6dTMtChbHmpe64KZ/nJjv2o0NcysTyVFq88= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251222041542-7fa506facd17/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251222041542-7fa506facd17 h1:DkPqqL8AH/Lj2i+0z7/7qmDCIhveKp+TXNcxI6ZF0Xg= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251222041542-7fa506facd17 h1:Is0ifg/hoF00wFvaHDLqAuEb4Jl/bENQq6x6jRhubV0= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251222041542-7fa506facd17 h1:2r2ikm5/GGypJOHfBpCMgOZoYzv+aV3yvaH/SPeHfPI= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251222041542-7fa506facd17 h1:j3fEUwtZ2LGrO6PzmgEGLMl5fea5wWi3SdilRugIICM= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251222041542-7fa506facd17 h1:kurrkp2s0NJhjJZYwGX/vi3BxO9Fb35BWApx70G4dlI= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251222041542-7fa506facd17 h1:cDHzVFgU2ltySWhYWfH2sOuOn+EdgK9z+l46jrIFyhw= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251222041542-7fa506facd17 h1:7U/ckrSuJ97R8aN+9B2MNc7F2vVE+2yBa5rsojYSJcA= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251222041542-7fa506facd17/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251222041542-7fa506facd17 h1:IfmxdknkJuwdMIlMLLtKLUdeZLlUTGmOT9G3AatLQLA= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251222041542-7fa506facd17 h1:DscNAuZZ8R1ubT6z2pIyZ+sVTIaoE7d7Y+PpJq5pTxY= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251222041542-7fa506facd17 h1:vNFAvJYE+8eTMbLC4B8Bx6lxG1SFA3yTd7QqFNAo0fE= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251222041542-7fa506facd17 h1:dEqWelap2tzWTtp7tUGEvvGdgPanBQceU2Kszn2A4Uc= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251222041542-7fa506facd17/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251222041542-7fa506facd17 h1:JYD1fglGfabg0/rQv4NT3DpOUMw2cYnp9ywa+zdFjDI= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251222041542-7fa506facd17 h1:oGurnJbJF2jUx8qt5Hge5m8bZ+bC0bg7Rj1QIy+iIig= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251222041542-7fa506facd17 h1:3TpHov3UIWwFcoGMO9d4+V6wAZZL2FaGg2YQSKxXT3I= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251222041542-7fa506facd17 h1:BY6yotGRpBWO++kJ4Y4ny4aJQLLe674KOrZfBT5Kf0A= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251222041542-7fa506facd17 h1:tKhSyhd0YdwnMMrxionY5Tii3f1S7L8VPn2prliEVrY= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251222041542-7fa506facd17 h1:4lhYLzCAQVoOzt3IEjSApyIW4yNujJ5ppemztvi4rhE= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251222041542-7fa506facd17 h1:PQjICo8R+uWDyssgqsno3Xh3YJ/qYQHEqXW3FYPSi+Y= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251222041542-7fa506facd17 h1:dba+qxmctcQ7jvOufwICi92u7NQ03DGPlB+KPPzpBWg= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index 53e3e3a4..e78537bd 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -175,7 +175,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL default: return nil, E.New("unknown quic congestion control: ", options.QUICCongestionControl) } - client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{ + client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ Context: ctx, ServerAddress: serverAddress, ServerName: serverName, From 511d1bb3fafe41c0ef35cfd531719850515ee5c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 25 Dec 2025 15:49:12 +0800 Subject: [PATCH 072/185] Update tailscale to v1.92.4 --- go.mod | 16 +++++----------- go.sum | 37 ++++++++++--------------------------- service/derp/service.go | 17 +++++++++-------- 3 files changed, 24 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index 13d1082d..bb9327bc 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8 github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 @@ -68,17 +68,15 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/database64128/netx-go v0.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect - github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/ebitengine/purego v0.9.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gaissmai/bart v0.18.0 // indirect - github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect + github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect @@ -86,22 +84,19 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/libdns/libdns v1.1.1 // indirect - github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect - github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pires/go-proxyproto v0.8.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect @@ -135,10 +130,8 @@ require ( github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect - github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect - github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -149,6 +142,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect + golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect diff --git a/go.sum b/go.sum index 0c4235b0..75397c3f 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= -github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= @@ -38,8 +36,6 @@ github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbww github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -54,8 +50,8 @@ github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= -github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -81,16 +77,12 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= -github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= -github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= 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-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU= @@ -100,8 +92,8 @@ github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUF github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8= @@ -112,12 +104,8 @@ github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= @@ -136,6 +124,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -230,8 +220,8 @@ github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkV github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos= -github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8 h1:+rb3fIFwFxhCkIt8B/V3bXWZmiNwDWBd22jQMxqY92w= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8/go.mod h1:HZxL3asFIkcIJtHdnqsdcXsY6d+1iMtq0SPUlX17TGM= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= @@ -252,14 +242,10 @@ github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPx github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= -github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= -github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -274,7 +260,6 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -323,11 +308,11 @@ 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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -369,8 +354,6 @@ gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa 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= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= diff --git a/service/derp/service.go b/service/derp/service.go index 049a5e4d..6cc1b9b6 100644 --- a/service/derp/service.go +++ b/service/derp/service.go @@ -39,6 +39,7 @@ import ( "github.com/sagernet/tailscale/client/local" "github.com/sagernet/tailscale/derp" "github.com/sagernet/tailscale/derp/derphttp" + "github.com/sagernet/tailscale/derp/derpserver" "github.com/sagernet/tailscale/net/netmon" "github.com/sagernet/tailscale/net/stun" "github.com/sagernet/tailscale/net/wsconn" @@ -62,7 +63,7 @@ type Service struct { listener *listener.Listener stunListener *listener.Listener tlsConfig tls.ServerConfig - server *derp.Server + server *derpserver.Server configPath string verifyClientEndpoint []string verifyClientURL []*option.DERPVerifyClientURLOptions @@ -141,7 +142,7 @@ func (d *Service) Start(stage adapter.StartStage) error { return err } - server := derp.NewServer(config.PrivateKey, func(format string, args ...any) { + server := derpserver.New(config.PrivateKey, func(format string, args ...any) { d.logger.Debug(fmt.Sprintf(format, args...)) }) @@ -193,7 +194,7 @@ func (d *Service) Start(stage adapter.StartStage) error { d.server = server derpMux := http.NewServeMux() - derpHandler := derphttp.Handler(server) + derpHandler := derpserver.Handler(server) derpHandler = addWebSocketSupport(server, derpHandler) derpMux.Handle("/derp", derpHandler) @@ -202,8 +203,8 @@ func (d *Service) Start(stage adapter.StartStage) error { return E.New("invalid home value: ", d.home) } - derpMux.HandleFunc("/derp/probe", derphttp.ProbeHandler) - derpMux.HandleFunc("/derp/latency-check", derphttp.ProbeHandler) + derpMux.HandleFunc("/derp/probe", derpserver.ProbeHandler) + derpMux.HandleFunc("/derp/latency-check", derpserver.ProbeHandler) derpMux.HandleFunc("/bootstrap-dns", tsweb.BrowserHeaderHandlerFunc(handleBootstrapDNS(d.ctx))) derpMux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tsweb.AddBrowserHeaders(w) @@ -213,7 +214,7 @@ func (d *Service) Start(stage adapter.StartStage) error { tsweb.AddBrowserHeaders(w) io.WriteString(w, "User-agent: *\nDisallow: /\n") })) - derpMux.Handle("/generate_204", http.HandlerFunc(derphttp.ServeNoContent)) + derpMux.Handle("/generate_204", http.HandlerFunc(derpserver.ServeNoContent)) err = d.tlsConfig.Start() if err != nil { @@ -289,7 +290,7 @@ func checkMeshKey(meshKey string) error { return nil } -func (d *Service) startMeshWithHost(derpServer *derp.Server, server *option.DERPMeshOptions) error { +func (d *Service) startMeshWithHost(derpServer *derpserver.Server, server *option.DERPMeshOptions) error { meshDialer, err := dialer.NewWithOptions(dialer.Options{ Context: d.ctx, Options: server.DialerOptions, @@ -400,7 +401,7 @@ func getHomeHandler(val string) (_ http.Handler, ok bool) { return nil, false } -func addWebSocketSupport(s *derp.Server, base http.Handler) http.Handler { +func addWebSocketSupport(s *derpserver.Server, base http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { up := strings.ToLower(r.Header.Get("Upgrade")) From e392c70b6fc478883afc0f3c7751fe64f6a741ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 25 Dec 2025 15:28:48 +0800 Subject: [PATCH 073/185] Ignore darwin IP_DONTFRAG error when not supported --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bb9327bc..b85c7920 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 - github.com/sagernet/sing v0.8.0-beta.7 + github.com/sagernet/sing v0.8.0-beta.8 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.7 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 75397c3f..5f72acfd 100644 --- a/go.sum +++ b/go.sum @@ -202,8 +202,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 h1:E9yZrU0ZxSiW5RrGUnFZeI02EIMdAAv0RxdoxXCqZyk= github.com/sagernet/quic-go v0.58.0-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.7 h1:RGM+r+Mrq7BVt3fLHJXgFzNWmtAfqgWaDkEkb4KUlBM= -github.com/sagernet/sing v0.8.0-beta.7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.8 h1:hUo0wZ2HGTieV1flEIai96HFhF34mMHVnduRqJHQvxg= +github.com/sagernet/sing v0.8.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.7 h1:Sh6KltQ6nB69S9ZdDKs5oARqkyY99gOaHe1JPxNV7ag= From a34868468ff169b7692a894df9170fd0241281a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 26 Dec 2025 11:49:49 +0800 Subject: [PATCH 074/185] Fix cronet on iOS --- .github/CRONET_GO_VERSION | 2 +- go.mod | 48 ++++++++++---------- go.sum | 96 +++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index c320cf1a..84a69fd0 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -b0385d27c2ab659d9532d71f301deb6599c44a79 +ced05691cdd4e758286db059830ff034807da687 diff --git a/go.mod b/go.mod index b85c7920..228ddf6d 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251222042025-2235d8db9f7f - github.com/sagernet/cronet-go/all v0.0.0-20251222042025-2235d8db9f7f + github.com/sagernet/cronet-go v0.0.0-20251225133447-a96cfe4f6a69 + github.com/sagernet/cronet-go/all v0.0.0-20251225133447-a96cfe4f6a69 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -101,28 +101,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251222041542-7fa506facd17 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251222041542-7fa506facd17 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index 5f72acfd..df1e0906 100644 --- a/go.sum +++ b/go.sum @@ -142,54 +142,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251222042025-2235d8db9f7f h1:F8MAWpgYSZp9xAuvmiqRRF2fydn49qbao3pFduuClYw= -github.com/sagernet/cronet-go v0.0.0-20251222042025-2235d8db9f7f/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251222042025-2235d8db9f7f h1:oxWJoIIF19Zcp438OTQLtXhFHcYzjw+QRjwhhOzRRrQ= -github.com/sagernet/cronet-go/all v0.0.0-20251222042025-2235d8db9f7f/go.mod h1:TTaiEGVltL4Dzde1pL5f8hxbsNhOZmsEBEUC0yqGiyw= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251222041542-7fa506facd17 h1:Mi/DrxIKK20bkvr8qV6Fxa/dc6yoRpyFkqe/HcVwxYE= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251222041542-7fa506facd17/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251222041542-7fa506facd17 h1:T07c63qkYlZCs2nDZL4Tt2FJQFKWoj6GThlw3Osq3PY= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251222041542-7fa506facd17 h1:wDect54Q6dTMtChbHmpe64KZ/nJjv2o0NcysTyVFq88= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251222041542-7fa506facd17/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251222041542-7fa506facd17 h1:DkPqqL8AH/Lj2i+0z7/7qmDCIhveKp+TXNcxI6ZF0Xg= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251222041542-7fa506facd17 h1:Is0ifg/hoF00wFvaHDLqAuEb4Jl/bENQq6x6jRhubV0= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251222041542-7fa506facd17 h1:2r2ikm5/GGypJOHfBpCMgOZoYzv+aV3yvaH/SPeHfPI= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251222041542-7fa506facd17 h1:j3fEUwtZ2LGrO6PzmgEGLMl5fea5wWi3SdilRugIICM= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251222041542-7fa506facd17 h1:kurrkp2s0NJhjJZYwGX/vi3BxO9Fb35BWApx70G4dlI= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251222041542-7fa506facd17 h1:cDHzVFgU2ltySWhYWfH2sOuOn+EdgK9z+l46jrIFyhw= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251222041542-7fa506facd17 h1:7U/ckrSuJ97R8aN+9B2MNc7F2vVE+2yBa5rsojYSJcA= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251222041542-7fa506facd17/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251222041542-7fa506facd17 h1:IfmxdknkJuwdMIlMLLtKLUdeZLlUTGmOT9G3AatLQLA= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251222041542-7fa506facd17 h1:DscNAuZZ8R1ubT6z2pIyZ+sVTIaoE7d7Y+PpJq5pTxY= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251222041542-7fa506facd17 h1:vNFAvJYE+8eTMbLC4B8Bx6lxG1SFA3yTd7QqFNAo0fE= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251222041542-7fa506facd17 h1:dEqWelap2tzWTtp7tUGEvvGdgPanBQceU2Kszn2A4Uc= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251222041542-7fa506facd17/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251222041542-7fa506facd17 h1:JYD1fglGfabg0/rQv4NT3DpOUMw2cYnp9ywa+zdFjDI= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251222041542-7fa506facd17 h1:oGurnJbJF2jUx8qt5Hge5m8bZ+bC0bg7Rj1QIy+iIig= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251222041542-7fa506facd17 h1:3TpHov3UIWwFcoGMO9d4+V6wAZZL2FaGg2YQSKxXT3I= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251222041542-7fa506facd17/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251222041542-7fa506facd17 h1:BY6yotGRpBWO++kJ4Y4ny4aJQLLe674KOrZfBT5Kf0A= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251222041542-7fa506facd17 h1:tKhSyhd0YdwnMMrxionY5Tii3f1S7L8VPn2prliEVrY= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251222041542-7fa506facd17 h1:4lhYLzCAQVoOzt3IEjSApyIW4yNujJ5ppemztvi4rhE= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251222041542-7fa506facd17/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251222041542-7fa506facd17 h1:PQjICo8R+uWDyssgqsno3Xh3YJ/qYQHEqXW3FYPSi+Y= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251222041542-7fa506facd17 h1:dba+qxmctcQ7jvOufwICi92u7NQ03DGPlB+KPPzpBWg= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251222041542-7fa506facd17/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251225133447-a96cfe4f6a69 h1:5K8s47TvmoS6YhpuOu8cUx1fiyu/gzGMnp4s/56eBPg= +github.com/sagernet/cronet-go v0.0.0-20251225133447-a96cfe4f6a69/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251225133447-a96cfe4f6a69 h1:noutQedWtKA4Ry5HxAC0egq1/jPXhQLNGOhSBTvya+k= +github.com/sagernet/cronet-go/all v0.0.0-20251225133447-a96cfe4f6a69/go.mod h1:u8a+w3gHb1QMe5f3GeF6doqIb31ih4AvBCUiBMWK5Bg= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251225132957-e1f82dabebce h1:CyXJpJcFo2AFAq5hNDB1OeBRK/KR8CCeGPVwNXLaqi0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251225132957-e1f82dabebce h1:awEG+TXzD4znoMCuTTJeMVyk9aH1tlabPOvu8cpnCWA= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251225132957-e1f82dabebce h1:FYYqebFnu0O3hl2N/ocq3ihQhSLfghFwubufRmQ0JQs= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251225132957-e1f82dabebce/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251225132957-e1f82dabebce h1:DWRUyY8Qdvso655bwlQxsIcIZoX1LdWkua4dVCJAKm8= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251225132957-e1f82dabebce h1:BIfMCrlftqJ05lQ6fmreS6fMqh+d31c+IRF4SEBP0EY= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251225132957-e1f82dabebce h1:S9kEuMdmx9QDoyCvBNmSs1wlV8KvdIYEdRu0tjeVnZQ= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251225132957-e1f82dabebce h1:EBNErcQd9OPNKUQ9Jo46GA1uzNZ8RZNFaBkowTlfcSU= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251225132957-e1f82dabebce h1:ySs/dm/1beLRapdP7O1nkex2vCNhttOwz0lnicWdAmU= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251225132957-e1f82dabebce h1:XiBQoMqRJnHNNBss517YO6YVtpe0Ib2JbrtApbHvl6k= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251225132957-e1f82dabebce h1:JpqQvxgAH8Eb1IAOu2nj/DjDqEyGhribM+wQXNW2/vE= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251225132957-e1f82dabebce h1:+SrPJfEs+Pp24O7agLam0q3DMgB64KCxAybsIGW1EUI= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251225132957-e1f82dabebce h1:i/sy1n46uteSLFnfGAchey68xTvZuxXzF+Qtn9X9ABk= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251225132957-e1f82dabebce h1:D0BPW5r0GphBcFfXMcQeD/9DCs1Y0PFpW2cHZ7ec7jc= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251225132957-e1f82dabebce h1:qO+gLXeZmqVIki2mbtJnjUNhDbaAJB4t0Mb8YNO+ySM= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251225132957-e1f82dabebce/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251225132957-e1f82dabebce h1:wthfnFHICUX687wgrL6YL1LYHJrfATWUz76jRIZ7EA0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251225132957-e1f82dabebce h1:gQMi9YHQvAMq5yzXpoWZ7aCCtnBVvVryCnpONc6Xvtg= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251225132957-e1f82dabebce h1:NkzbN5KmVJLbXkenOTeiCBOLJQBi3vBlKYFtuQ4nK4w= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251225132957-e1f82dabebce h1:a76cCOcshqixwueb56hnGcUyyad/2M34nxTgLDa8xo4= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251225132957-e1f82dabebce h1:OU2WMFXlC2IsBHi2xrLWROa3Pw2dNBJ5RAwIFBNg9lM= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251225132957-e1f82dabebce h1:YCmnoOcnZtaQuoy9YX0AKhXCuDYYn21hrNa7sMkjM7k= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251225132957-e1f82dabebce h1:a1qP/Kr90j9rHzsw1aOe2o9auDey9GbyJchh99xWMRc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251225132957-e1f82dabebce h1:q5AKReJPyQndi9+/fYVTx7Jgoy8Q+6lHVnbzoiPauQE= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= From 78b4eac974f0fcf0530a4b1ed206783952d6c181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 26 Dec 2025 15:52:28 +0800 Subject: [PATCH 075/185] Add pre-match support for auto redirect --- constant/rule.go | 1 + docs/configuration/inbound/tun.md | 20 ++++++++ docs/configuration/inbound/tun.zh.md | 20 ++++++++ docs/configuration/route/rule_action.md | 35 ++++++++++++++ docs/configuration/route/rule_action.zh.md | 34 +++++++++++++ docs/configuration/shared/pre-match.md | 39 +++++++++++++++ docs/configuration/shared/pre-match.zh.md | 37 ++++++++++++++ go.mod | 3 +- go.sum | 6 ++- mkdocs.yml | 1 + option/rule_action.go | 14 +++++- option/tun.go | 2 + protocol/tailscale/endpoint.go | 11 ++++- protocol/tun/inbound.go | 56 +++++++++++++++++++++- protocol/wireguard/endpoint.go | 11 ++++- route/route.go | 27 ++++++++++- route/rule/rule_action.go | 48 +++++++++++++++++++ 17 files changed, 354 insertions(+), 11 deletions(-) create mode 100644 docs/configuration/shared/pre-match.md create mode 100644 docs/configuration/shared/pre-match.zh.md diff --git a/constant/rule.go b/constant/rule.go index b565a39d..55cad2e1 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -30,6 +30,7 @@ const ( RuleActionTypeRoute = "route" RuleActionTypeRouteOptions = "route-options" RuleActionTypeDirect = "direct" + RuleActionTypeBypass = "bypass" RuleActionTypeReject = "reject" RuleActionTypeHijackDNS = "hijack-dns" RuleActionTypeSniff = "sniff" diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 5dde6b20..0f1676a7 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -4,6 +4,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" + :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) + :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) :material-plus: [exclude_mptcp](#exclude_mptcp) !!! quote "Changes in sing-box 1.12.0" @@ -67,6 +69,8 @@ icon: material/new-box "auto_redirect": true, "auto_redirect_input_mark": "0x2023", "auto_redirect_output_mark": "0x2024", + "auto_redirect_reset_mark": "0x2025", + "auto_redirect_nfqueue": 100, "exclude_mptcp": false, "loopback_address": [ "10.7.0.1" @@ -283,6 +287,22 @@ Connection output mark used by `auto_redirect`. `0x2024` is used by default. +#### auto_redirect_reset_mark + +!!! question "Since sing-box 1.13.0" + +Connection reset mark used by `auto_redirect` pre-matching. + +`0x2025` is used by default. + +#### auto_redirect_nfqueue + +!!! question "Since sing-box 1.13.0" + +NFQueue number used by `auto_redirect` pre-matching. + +`100` is used by default. + #### exclude_mptcp !!! question "Since sing-box 1.13.0" diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index e9dec46f..e7c93270 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -4,6 +4,8 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" + :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) + :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) :material-plus: [exclude_mptcp](#exclude_mptcp) !!! quote "sing-box 1.12.0 中的更改" @@ -67,6 +69,8 @@ icon: material/new-box "auto_redirect": true, "auto_redirect_input_mark": "0x2023", "auto_redirect_output_mark": "0x2024", + "auto_redirect_reset_mark": "0x2025", + "auto_redirect_nfqueue": 100, "exclude_mptcp": false, "loopback_address": [ "10.7.0.1" @@ -282,6 +286,22 @@ tun 接口的 IPv6 前缀。 默认使用 `0x2024`。 +#### auto_redirect_reset_mark + +!!! question "自 sing-box 1.13.0 起" + +`auto_redirect` 预匹配使用的连接重置标记。 + +默认使用 `0x2025`。 + +#### auto_redirect_nfqueue + +!!! question "自 sing-box 1.13.0 起" + +`auto_redirect` 预匹配使用的 NFQueue 编号。 + +默认使用 `100`。 + #### exclude_mptcp !!! question "自 sing-box 1.13.0 起" diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index a57de60f..641ebb2c 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -4,6 +4,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" + :material-plus: [bypass](#bypass) :material-alert: [reject](#reject) !!! quote "Changes in sing-box 1.12.0" @@ -44,6 +45,40 @@ Tag of target outbound. See `route-options` fields below. +### bypass + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux with `auto_redirect` enabled. + +```json +{ + "action": "bypass", + "outbound": "", + + ... // route-options Fields +} +``` + +`bypass` routes connection to the specified outbound. + +For tun connections in [pre-match](/configuration/shared/pre-match/), +the connection will bypass sing-box and connect directly at the kernel level. + +For non-tun connections and already established connections, the behavior is the same as `route`. + +#### outbound + +==Required== + +Tag of target outbound. + +#### route-options Fields + +See `route-options` fields below. + ### reject !!! quote "Changes in sing-box 1.13.0" diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 98ea1227..d06e6ee6 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -4,6 +4,7 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" + :material-plus: [bypass](#bypass) :material-alert: [reject](#reject) !!! quote "sing-box 1.12.0 中的更改" @@ -40,6 +41,39 @@ icon: material/new-box 参阅下方的 `route-options` 字段。 +### bypass + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux,且需要启用 `auto_redirect`。 + +```json +{ + "action": "bypass", + "outbound": "", + + ... // route-options 字段 +} +``` + +`bypass` 将连接路由到指定出站。 + +对于[预匹配](/configuration/shared/pre-match/)中的 tun 连接,连接将在内核层面绕过 sing-box 直接连接。 + +对于非 tun 连接和已建立的连接,行为与 `route` 相同。 + +#### outbound + +==必填== + +目标出站的标签。 + +#### route-options 字段 + +参阅下方的 `route-options` 字段。 + ### reject !!! quote "sing-box 1.13.0 中的更改" diff --git a/docs/configuration/shared/pre-match.md b/docs/configuration/shared/pre-match.md new file mode 100644 index 00000000..180099aa --- /dev/null +++ b/docs/configuration/shared/pre-match.md @@ -0,0 +1,39 @@ +--- +icon: material/new-box +--- + +# Pre-match + +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [bypass](#bypass) + +Pre-match is rule matching that runs before the connection is established. + +### How it works + +When TUN receives a connection request, the connection has not yet been established, +so no connection data can be read. In this phase, sing-box runs the routing rules in pre-match mode. + +Since connection data is unavailable, only actions that do not require connection data can be executed. +When a rule matches an action that requires an established connection, pre-match stops at that rule. + +### Supported actions + +#### reject + +Reject with TCP RST / ICMP unreachable. + +#### route + +Route ICMP connections to the specified outbound for direct reply. + +#### bypass + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux with `auto_redirect` enabled. + +Bypass sing-box and connect directly at kernel level. diff --git a/docs/configuration/shared/pre-match.zh.md b/docs/configuration/shared/pre-match.zh.md new file mode 100644 index 00000000..c615070f --- /dev/null +++ b/docs/configuration/shared/pre-match.zh.md @@ -0,0 +1,37 @@ +--- +icon: material/new-box +--- + +# 预匹配 + +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [bypass](#bypass) + +预匹配是在连接建立之前运行的规则匹配。 + +### 工作原理 + +当 TUN 收到连接请求时,连接尚未建立,因此无法读取连接数据。在此阶段,sing-box 在预匹配模式下运行路由规则。 + +由于连接数据不可用,只有不需要连接数据的动作才能执行。当规则匹配到需要已建立连接的动作时,预匹配将在该规则处停止。 + +### 支持的动作 + +#### reject + +以 TCP RST / ICMP 不可达拒绝。 + +#### route + +将 ICMP 连接路由到指定出站以直接回复。 + +#### bypass + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux,且需要启用 `auto_redirect`。 + +在内核层面绕过 sing-box 直接连接。 diff --git a/go.mod b/go.mod index 228ddf6d..ff22d815 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e + github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8 @@ -73,6 +73,7 @@ require ( github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/ebitengine/purego v0.9.1 // indirect + github.com/florianl/go-nfqueue/v2 v2.0.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gaissmai/bart v0.18.0 // indirect diff --git a/go.sum b/go.sum index df1e0906..36cbc7c9 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbY github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE= +github.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -214,8 +216,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e h1:ZEv+9vy7vC1vbr3LfwZGx3JAOkl/w4+hnGamHw4W36M= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8 h1:aIgk6YzS/7fNm92CycFWzithdwIc+NAwXGHAJce1dyM= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= diff --git a/mkdocs.yml b/mkdocs.yml index 236252b9..7683d376 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -122,6 +122,7 @@ nav: - Dial Fields: configuration/shared/dial.md - TLS: configuration/shared/tls.md - DNS01 Challenge Fields: configuration/shared/dns01_challenge.md + - Pre-match: configuration/shared/pre-match.md - Multiplex: configuration/shared/multiplex.md - V2Ray Transport: configuration/shared/v2ray-transport.md - UDP over TCP: configuration/shared/udp-over-tcp.md diff --git a/option/rule_action.go b/option/rule_action.go index e28b58eb..48326aed 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -18,6 +18,7 @@ type _RuleAction struct { RouteOptions RouteActionOptions `json:"-"` RouteOptionsOptions RouteOptionsActionOptions `json:"-"` DirectOptions DirectActionOptions `json:"-"` + BypassOptions RouteActionOptions `json:"-"` RejectOptions RejectActionOptions `json:"-"` SniffOptions RouteActionSniff `json:"-"` ResolveOptions RouteActionResolve `json:"-"` @@ -38,6 +39,8 @@ func (r RuleAction) MarshalJSON() ([]byte, error) { v = r.RouteOptionsOptions case C.RuleActionTypeDirect: v = r.DirectOptions + case C.RuleActionTypeBypass: + v = r.BypassOptions case C.RuleActionTypeReject: v = r.RejectOptions case C.RuleActionTypeHijackDNS: @@ -69,6 +72,8 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { v = &r.RouteOptionsOptions case C.RuleActionTypeDirect: v = &r.DirectOptions + case C.RuleActionTypeBypass: + v = &r.BypassOptions case C.RuleActionTypeReject: v = &r.RejectOptions case C.RuleActionTypeHijackDNS: @@ -84,7 +89,14 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { // check unknown fields return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) } - return badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v) + err = badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v) + if err != nil { + return err + } + if r.Action == C.RuleActionTypeBypass && r.BypassOptions.Outbound == "" { + return E.New("missing outbound for bypass action") + } + return nil } type _DNSRuleAction struct { diff --git a/option/tun.go b/option/tun.go index ca8e3a11..48989c9f 100644 --- a/option/tun.go +++ b/option/tun.go @@ -20,6 +20,8 @@ type TunInboundOptions struct { AutoRedirect bool `json:"auto_redirect,omitempty"` AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` + AutoRedirectResetMark FwMark `json:"auto_redirect_reset_mark,omitempty"` + AutoRedirectNFQueue uint16 `json:"auto_redirect_nfqueue,omitempty"` ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"` LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"` StrictRoute bool `json:"strict_route,omitempty"` diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index e7e4f925..ca5be736 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -481,8 +481,15 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina Destination: destination, }, routeContext, timeout) if err != nil { - if !rule.IsRejected(err) { - t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + switch { + case rule.IsBypassed(err): + err = nil + case rule.IsRejected(err): + t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } } } return routeDestination, err diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 1cb2f309..43f6680c 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -182,6 +182,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if outputMark == 0 { outputMark = tun.DefaultAutoRedirectOutputMark } + resetMark := uint32(options.AutoRedirectResetMark) + if resetMark == 0 { + resetMark = tun.DefaultAutoRedirectResetMark + } + nfQueue := options.AutoRedirectNFQueue + if nfQueue == 0 { + nfQueue = tun.DefaultAutoRedirectNFQueue + } networkManager := service.FromContext[adapter.NetworkManager](ctx) multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000)) inbound := &Inbound{ @@ -202,6 +210,8 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo IPRoute2RuleIndex: ruleIndex, AutoRedirectInputMark: inputMark, AutoRedirectOutputMark: outputMark, + AutoRedirectResetMark: resetMark, + AutoRedirectNFQueue: nfQueue, ExcludeMPTCP: options.ExcludeMPTCP, Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4), Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6), @@ -472,8 +482,15 @@ func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destinat InboundOptions: t.inboundOptions, }, routeContext, timeout) if err != nil { - if !rule.IsRejected(err) { - t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + switch { + case rule.IsBypassed(err): + err = nil + case rule.IsRejected(err): + t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } } } return routeDestination, err @@ -509,6 +526,37 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, type autoRedirectHandler Inbound +func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { + var ipVersion uint8 + if !destination.IsIPv6() { + ipVersion = 4 + } else { + ipVersion = 6 + } + routeDestination, err := t.router.PreMatch(adapter.InboundContext{ + Inbound: t.tag, + InboundType: C.TypeTun, + IPVersion: ipVersion, + Network: network, + Source: source, + Destination: destination, + InboundOptions: t.inboundOptions, + }, routeContext, timeout) + if err != nil { + switch { + case rule.IsBypassed(err): + t.logger.Trace("bypass ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + case rule.IsRejected(err): + t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } + } + } + return routeDestination, err +} + func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext @@ -522,3 +570,7 @@ func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } + +func (t *autoRedirectHandler) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + panic("unexcepted") +} diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 811c6bb4..a68d3b36 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -142,8 +142,15 @@ func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina Destination: destination, }, routeContext, timeout) if err != nil { - if !rule.IsRejected(err) { - w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + switch { + case rule.IsBypassed(err): + err = nil + case rule.IsRejected(err): + w.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) + default: + if network == N.NetworkICMP { + w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())) + } } } return routeDestination, err diff --git a/route/route.go b/route/route.go index 2f1d01f7..2e4b854a 100644 --- a/route/route.go +++ b/route/route.go @@ -113,6 +113,17 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad buf.ReleaseMulti(buffers) return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) } + case *R.RuleActionBypass: + var loaded bool + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) + if !loaded { + buf.ReleaseMulti(buffers) + return E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(selectedOutbound.Network(), N.NetworkTCP) { + buf.ReleaseMulti(buffers) + return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) + } case *R.RuleActionReject: buf.ReleaseMulti(buffers) if action.Method == C.RuleActionRejectMethodReply { @@ -231,6 +242,17 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m N.ReleaseMultiPacketBuffer(packetBuffers) return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) } + case *R.RuleActionBypass: + var loaded bool + selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) + if !loaded { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(selectedOutbound.Network(), N.NetworkUDP) { + N.ReleaseMultiPacketBuffer(packetBuffers) + return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) + } case *R.RuleActionReject: N.ReleaseMultiPacketBuffer(packetBuffers) if action.Method == C.RuleActionRejectMethodReply { @@ -287,6 +309,8 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire } } return nil, action.Error(context.Background()) + case *R.RuleActionBypass: + return nil, &R.BypassedError{Cause: tun.ErrBypass} case *R.RuleActionRoute: if routeContext == nil { return nil, nil @@ -567,7 +591,8 @@ match: actionType := currentRule.Action().Type() if actionType == C.RuleActionTypeRoute || actionType == C.RuleActionTypeReject || - actionType == C.RuleActionTypeHijackDNS { + actionType == C.RuleActionTypeHijackDNS || + actionType == C.RuleActionTypeBypass { selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index 5c08109a..f4608e0a 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -56,6 +56,21 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti TLSFragmentFallbackDelay: time.Duration(action.RouteOptionsOptions.TLSFragmentFallbackDelay), TLSRecordFragment: action.RouteOptionsOptions.TLSRecordFragment, }, nil + case C.RuleActionTypeBypass: + return &RuleActionBypass{ + Outbound: action.BypassOptions.Outbound, + RuleActionRouteOptions: RuleActionRouteOptions{ + OverrideAddress: M.ParseSocksaddrHostPort(action.BypassOptions.OverrideAddress, 0), + OverridePort: action.BypassOptions.OverridePort, + NetworkStrategy: (*C.NetworkStrategy)(action.BypassOptions.NetworkStrategy), + FallbackDelay: time.Duration(action.BypassOptions.FallbackDelay), + UDPDisableDomainUnmapping: action.BypassOptions.UDPDisableDomainUnmapping, + UDPConnect: action.BypassOptions.UDPConnect, + TLSFragment: action.BypassOptions.TLSFragment, + TLSFragmentFallbackDelay: time.Duration(action.BypassOptions.TLSFragmentFallbackDelay), + TLSRecordFragment: action.BypassOptions.TLSRecordFragment, + }, + }, nil case C.RuleActionTypeDirect: directDialer, err := dialer.New(ctx, option.DialerOptions(action.DirectOptions), false) if err != nil { @@ -158,6 +173,22 @@ func (r *RuleActionRoute) String() string { return F.ToString("route(", strings.Join(descriptions, ","), ")") } +type RuleActionBypass struct { + Outbound string + RuleActionRouteOptions +} + +func (r *RuleActionBypass) Type() string { + return C.RuleActionTypeBypass +} + +func (r *RuleActionBypass) String() string { + var descriptions []string + descriptions = append(descriptions, r.Outbound) + descriptions = append(descriptions, r.Descriptions()...) + return F.ToString("bypass(", strings.Join(descriptions, ","), ")") +} + type RuleActionRouteOptions struct { OverrideAddress M.Socksaddr OverridePort uint16 @@ -301,6 +332,23 @@ func IsRejected(err error) bool { return errors.As(err, &rejected) } +type BypassedError struct { + Cause error +} + +func (b *BypassedError) Error() string { + return "bypassed" +} + +func (b *BypassedError) Unwrap() error { + return b.Cause +} + +func IsBypassed(err error) bool { + var bypassed *BypassedError + return errors.As(err, &bypassed) +} + type RuleActionReject struct { Method string NoDrop bool From bf4a9edc89c2d0cbbaf5e9a0f1cdfc2f0dbca14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 26 Dec 2025 15:58:13 +0800 Subject: [PATCH 076/185] Fix panic when closing Box before Start with file log output --- log/log.go | 1 + 1 file changed, 1 insertion(+) diff --git a/log/log.go b/log/log.go index 7b8f2843..3a1c6537 100644 --- a/log/log.go +++ b/log/log.go @@ -40,6 +40,7 @@ func New(options Options) (Factory, error) { case "stdout": logWriter = os.Stdout default: + logWriter = io.Discard logFilePath = logOptions.Output } logFormatter := Formatter{ From 8ae16aa4524f8b018d6012a85e245588124ac8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 26 Dec 2025 16:19:26 +0800 Subject: [PATCH 077/185] Add format_docs command for documentation trailing space formatting --- Makefile | 3 + cmd/internal/format_docs/main.go | 117 +++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 cmd/internal/format_docs/main.go diff --git a/Makefile b/Makefile index bd70837e..79b32dd4 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,9 @@ fmt: @gofmt -s -w . @gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" . +fmt_docs: + go run ./cmd/internal/format_docs + fmt_install: go install -v mvdan.cc/gofumpt@latest go install -v github.com/daixiang0/gci@latest diff --git a/cmd/internal/format_docs/main.go b/cmd/internal/format_docs/main.go new file mode 100644 index 00000000..061b2121 --- /dev/null +++ b/cmd/internal/format_docs/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "bytes" + "os" + "path/filepath" + "strings" + + "github.com/sagernet/sing-box/log" +) + +func main() { + err := filepath.Walk("docs", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if !strings.HasSuffix(path, ".md") { + return nil + } + return processFile(path) + }) + if err != nil { + log.Fatal(err) + } +} + +func processFile(path string) error { + content, err := os.ReadFile(path) + if err != nil { + return err + } + + lines := strings.Split(string(content), "\n") + modified := false + result := make([]string, 0, len(lines)) + + inQuoteBlock := false + materialLines := []int{} // indices of :material- lines in the block + + for _, line := range lines { + // Check for quote block start + if strings.HasPrefix(line, "!!! quote \"") && strings.Contains(line, "sing-box") { + inQuoteBlock = true + materialLines = nil + result = append(result, line) + continue + } + + // Inside a quote block + if inQuoteBlock { + trimmed := strings.TrimPrefix(line, " ") + isMaterialLine := strings.HasPrefix(trimmed, ":material-") + isEmpty := strings.TrimSpace(line) == "" + isIndented := strings.HasPrefix(line, " ") + + if isMaterialLine { + materialLines = append(materialLines, len(result)) + result = append(result, line) + continue + } + + // Block ends when: + // - Empty line AFTER we've seen material lines, OR + // - Non-indented, non-empty line + blockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented) + if blockEnds { + // Process collected material lines + if len(materialLines) > 0 { + for j, idx := range materialLines { + isLast := j == len(materialLines)-1 + resultLine := strings.TrimRight(result[idx], " ") + if !isLast { + // Add trailing two spaces for non-last lines + resultLine += " " + } + if result[idx] != resultLine { + modified = true + result[idx] = resultLine + } + } + } + inQuoteBlock = false + materialLines = nil + } + } + + result = append(result, line) + } + + // Handle case where file ends while still in a block + if inQuoteBlock && len(materialLines) > 0 { + for j, idx := range materialLines { + isLast := j == len(materialLines)-1 + resultLine := strings.TrimRight(result[idx], " ") + if !isLast { + resultLine += " " + } + if result[idx] != resultLine { + modified = true + result[idx] = resultLine + } + } + } + + if modified { + newContent := strings.Join(result, "\n") + if !bytes.Equal(content, []byte(newContent)) { + log.Info("formatted: ", path) + return os.WriteFile(path, []byte(newContent), 0o644) + } + } + + return nil +} From 24b33a43fc4377f108326c86e168da744cb53f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 26 Dec 2025 16:20:27 +0800 Subject: [PATCH 078/185] documentation: Format changes header --- docs/configuration/dns/server/local.md | 2 +- docs/configuration/experimental/cache-file.md | 2 +- docs/configuration/experimental/cache-file.zh.md | 2 +- docs/configuration/inbound/tun.md | 6 +++--- docs/configuration/inbound/tun.zh.md | 8 ++++---- docs/configuration/outbound/wireguard.md | 2 +- docs/configuration/outbound/wireguard.zh.md | 2 +- docs/configuration/route/rule_action.md | 2 +- docs/configuration/route/rule_action.zh.md | 2 +- docs/configuration/shared/dial.md | 4 ++-- docs/configuration/shared/dial.zh.md | 4 ++-- docs/configuration/shared/dns01_challenge.md | 2 +- docs/configuration/shared/dns01_challenge.zh.md | 2 +- docs/configuration/shared/listen.md | 2 +- docs/configuration/shared/listen.zh.md | 2 +- docs/configuration/shared/tls.md | 2 +- docs/configuration/shared/tls.zh.md | 10 +++++----- docs/configuration/shared/wifi-state.md | 2 +- docs/configuration/shared/wifi-state.zh.md | 2 +- 19 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/configuration/dns/server/local.md b/docs/configuration/dns/server/local.md index 9556cb1d..aa7f095a 100644 --- a/docs/configuration/dns/server/local.md +++ b/docs/configuration/dns/server/local.md @@ -4,7 +4,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [prefer_go](#prefer_go) + :material-plus: [prefer_go](#prefer_go) !!! question "Since sing-box 1.12.0" diff --git a/docs/configuration/experimental/cache-file.md b/docs/configuration/experimental/cache-file.md index 18c430d9..4ad0361c 100644 --- a/docs/configuration/experimental/cache-file.md +++ b/docs/configuration/experimental/cache-file.md @@ -3,7 +3,7 @@ !!! quote "Changes in sing-box 1.9.0" :material-plus: [store_rdrc](#store_rdrc) - :material-plus: [rdrc_timeout](#rdrc_timeout) + :material-plus: [rdrc_timeout](#rdrc_timeout) ### Structure diff --git a/docs/configuration/experimental/cache-file.zh.md b/docs/configuration/experimental/cache-file.zh.md index 656d53c4..db2ae205 100644 --- a/docs/configuration/experimental/cache-file.zh.md +++ b/docs/configuration/experimental/cache-file.zh.md @@ -3,7 +3,7 @@ !!! quote "sing-box 1.9.0 中的更改" :material-plus: [store_rdrc](#store_rdrc) - :material-plus: [rdrc_timeout](#rdrc_timeout) + :material-plus: [rdrc_timeout](#rdrc_timeout) ### 结构 diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 0f1676a7..39393f42 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -4,8 +4,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) - :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) + :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) + :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) :material-plus: [exclude_mptcp](#exclude_mptcp) !!! quote "Changes in sing-box 1.12.0" @@ -40,7 +40,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.9.0" :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain) - :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) + :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) !!! quote "Changes in sing-box 1.8.0" diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index e7c93270..fa0c5d91 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -4,8 +4,8 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) - :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) + :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) + :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) :material-plus: [exclude_mptcp](#exclude_mptcp) !!! quote "sing-box 1.12.0 中的更改" @@ -28,7 +28,7 @@ icon: material/new-box :material-delete-clock: [inet6_route_address](#inet6_route_address) :material-plus: [route_exclude_address](#route_address) :material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address) - :material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address) + :material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address) :material-plus: [iproute2_table_index](#iproute2_table_index) :material-plus: [iproute2_rule_index](#iproute2_table_index) :material-plus: [auto_redirect](#auto_redirect) @@ -40,7 +40,7 @@ icon: material/new-box !!! quote "sing-box 1.9.0 中的更改" :material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain) - :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) + :material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain) !!! quote "sing-box 1.8.0 中的更改" diff --git a/docs/configuration/outbound/wireguard.md b/docs/configuration/outbound/wireguard.md index 96c5dc75..648ba607 100644 --- a/docs/configuration/outbound/wireguard.md +++ b/docs/configuration/outbound/wireguard.md @@ -12,7 +12,7 @@ icon: material/delete-clock !!! quote "Changes in sing-box 1.8.0" - :material-plus: [gso](#gso) + :material-plus: [gso](#gso) ### Structure diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index 46b49a94..3b22affd 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/docs/configuration/outbound/wireguard.zh.md @@ -12,7 +12,7 @@ icon: material/delete-clock !!! quote "sing-box 1.8.0 中的更改" - :material-plus: [gso](#gso) + :material-plus: [gso](#gso) ### 结构 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 641ebb2c..241233b9 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -4,7 +4,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [bypass](#bypass) + :material-plus: [bypass](#bypass) :material-alert: [reject](#reject) !!! quote "Changes in sing-box 1.12.0" diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index d06e6ee6..c944d1a8 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -4,7 +4,7 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [bypass](#bypass) + :material-plus: [bypass](#bypass) :material-alert: [reject](#reject) !!! quote "sing-box 1.12.0 中的更改" diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 634b951c..3907dcb2 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -4,8 +4,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) - :material-plus: [tcp_keep_alive](#tcp_keep_alive) + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [tcp_keep_alive](#tcp_keep_alive) :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) !!! quote "Changes in sing-box 1.12.0" diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 9778585b..23cea509 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -4,8 +4,8 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) - :material-plus: [tcp_keep_alive](#tcp_keep_alive) + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [tcp_keep_alive](#tcp_keep_alive) :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) !!! quote "sing-box 1.12.0 中的更改" diff --git a/docs/configuration/shared/dns01_challenge.md b/docs/configuration/shared/dns01_challenge.md index 23265bd6..904803e6 100644 --- a/docs/configuration/shared/dns01_challenge.md +++ b/docs/configuration/shared/dns01_challenge.md @@ -4,7 +4,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [alidns.security_token](#security_token) + :material-plus: [alidns.security_token](#security_token) :material-plus: [cloudflare.zone_token](#zone_token) ### Structure diff --git a/docs/configuration/shared/dns01_challenge.zh.md b/docs/configuration/shared/dns01_challenge.zh.md index c316a9fd..7fb89c11 100644 --- a/docs/configuration/shared/dns01_challenge.zh.md +++ b/docs/configuration/shared/dns01_challenge.zh.md @@ -4,7 +4,7 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [alidns.security_token](#security_token) + :material-plus: [alidns.security_token](#security_token) :material-plus: [cloudflare.zone_token](#zone_token) ### 结构 diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index 5da32ff0..55325564 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -4,7 +4,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) :material-alert: [tcp_keep_alive](#tcp_keep_alive) !!! quote "Changes in sing-box 1.12.0" diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index 90cf6a79..905cea3c 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -4,7 +4,7 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) + :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) :material-alert: [tcp_keep_alive](#tcp_keep_alive) !!! quote "sing-box 1.12.0 中的更改" diff --git a/docs/configuration/shared/tls.md b/docs/configuration/shared/tls.md index 0100e55a..73ceffcc 100644 --- a/docs/configuration/shared/tls.md +++ b/docs/configuration/shared/tls.md @@ -26,7 +26,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.10.0" - :material-alert-decagram: [utls](#utls) + :material-alert-decagram: [utls](#utls) ### Inbound diff --git a/docs/configuration/shared/tls.zh.md b/docs/configuration/shared/tls.zh.md index faf6d49a..e0460983 100644 --- a/docs/configuration/shared/tls.zh.md +++ b/docs/configuration/shared/tls.zh.md @@ -18,15 +18,15 @@ icon: material/new-box !!! quote "sing-box 1.12.0 中的更改" - :material-plus: [fragment](#fragment) - :material-plus: [fragment_fallback_delay](#fragment_fallback_delay) - :material-plus: [record_fragment](#record_fragment) - :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled) + :material-plus: [fragment](#fragment) + :material-plus: [fragment_fallback_delay](#fragment_fallback_delay) + :material-plus: [record_fragment](#record_fragment) + :material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled) :material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled) !!! quote "sing-box 1.10.0 中的更改" - :material-alert-decagram: [utls](#utls) + :material-alert-decagram: [utls](#utls) ### 入站 diff --git a/docs/configuration/shared/wifi-state.md b/docs/configuration/shared/wifi-state.md index b509d0e9..a32675b3 100644 --- a/docs/configuration/shared/wifi-state.md +++ b/docs/configuration/shared/wifi-state.md @@ -6,7 +6,7 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: Linux support + :material-plus: Linux support :material-plus: Windows support sing-box can monitor Wi-Fi state to enable routing rules based on `wifi_ssid` and `wifi_bssid`. diff --git a/docs/configuration/shared/wifi-state.zh.md b/docs/configuration/shared/wifi-state.zh.md index a44340cf..02e8b6c9 100644 --- a/docs/configuration/shared/wifi-state.zh.md +++ b/docs/configuration/shared/wifi-state.zh.md @@ -6,7 +6,7 @@ icon: material/new-box !!! quote "sing-box 1.13.0 的变更" - :material-plus: Linux 支持 + :material-plus: Linux 支持 :material-plus: Windows 支持 sing-box 可以监控 Wi-Fi 状态,以启用基于 `wifi_ssid` 和 `wifi_bssid` 的路由规则。 From 95ccb837d3cd3cf9ec4455b6a6e5133eb8b1d8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 27 Dec 2025 13:53:01 +0800 Subject: [PATCH 079/185] platform: Add GetStartedAt for StartedService --- daemon/started_service.go | 9 ++ daemon/started_service.pb.go | 150 +++++++++++++++++--------- daemon/started_service.proto | 5 + daemon/started_service_grpc.pb.go | 39 +++++++ experimental/libbox/command_client.go | 13 +++ 5 files changed, 166 insertions(+), 50 deletions(-) diff --git a/daemon/started_service.go b/daemon/started_service.go index 65adac5d..4f147cf8 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -50,6 +50,7 @@ type StartedService struct { logSubscriber *observable.Subscriber[*log.Entry] logObserver *observable.Observer[*log.Entry] instance *Instance + startedAt time.Time urlTestSubscriber *observable.Subscriber[struct{}] urlTestObserver *observable.Observer[struct{}] urlTestHistoryStorage *urltest.HistoryStorage @@ -193,6 +194,7 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov if err != nil { return s.updateStatusError(err) } + s.startedAt = time.Now() s.updateStatus(ServiceStatus_STARTED) s.serviceAccess.Unlock() runtime.GC() @@ -215,6 +217,7 @@ func (s *StartedService) CloseService() error { } } s.instance = nil + s.startedAt = time.Time{} s.updateStatus(ServiceStatus_IDLE) s.serviceAccess.Unlock() runtime.GC() @@ -803,6 +806,12 @@ func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *empty }, nil } +func (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty) (*StartedAt, error) { + s.serviceAccess.RLock() + defer s.serviceAccess.RUnlock() + return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil +} + func (s *StartedService) SubscribeHelperEvents(empty *emptypb.Empty, server grpc.ServerStreamingServer[HelperRequest]) error { return os.ErrInvalid } diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go index dcd94feb..3a820e6f 100644 --- a/daemon/started_service.pb.go +++ b/daemon/started_service.pb.go @@ -1567,6 +1567,50 @@ func (x *DeprecatedWarning) GetMigrationLink() string { return "" } +type StartedAt struct { + state protoimpl.MessageState `protogen:"open.v1"` + StartedAt int64 `protobuf:"varint,1,opt,name=startedAt,proto3" json:"startedAt,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StartedAt) Reset() { + *x = StartedAt{} + mi := &file_daemon_started_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StartedAt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartedAt) ProtoMessage() {} + +func (x *StartedAt) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartedAt.ProtoReflect.Descriptor instead. +func (*StartedAt) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{22} +} + +func (x *StartedAt) GetStartedAt() int64 { + if x != nil { + return x.StartedAt + } + return 0 +} + type Log_Message struct { state protoimpl.MessageState `protogen:"open.v1"` Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"` @@ -1577,7 +1621,7 @@ type Log_Message struct { func (x *Log_Message) Reset() { *x = Log_Message{} - mi := &file_daemon_started_service_proto_msgTypes[22] + mi := &file_daemon_started_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1589,7 +1633,7 @@ func (x *Log_Message) String() string { func (*Log_Message) ProtoMessage() {} func (x *Log_Message) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[22] + mi := &file_daemon_started_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1728,7 +1772,9 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x11DeprecatedWarning\x12\x18\n" + "\amessage\x18\x01 \x01(\tR\amessage\x12\x1c\n" + "\timpending\x18\x02 \x01(\bR\timpending\x12$\n" + - "\rmigrationLink\x18\x03 \x01(\tR\rmigrationLink*U\n" + + "\rmigrationLink\x18\x03 \x01(\tR\rmigrationLink\")\n" + + "\tStartedAt\x12\x1c\n" + + "\tstartedAt\x18\x01 \x01(\x03R\tstartedAt*U\n" + "\bLogLevel\x12\t\n" + "\x05PANIC\x10\x00\x12\t\n" + "\x05FATAL\x10\x01\x12\t\n" + @@ -1746,7 +1792,7 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x10ConnectionSortBy\x12\b\n" + "\x04DATE\x10\x00\x12\v\n" + "\aTRAFFIC\x10\x01\x12\x11\n" + - "\rTOTAL_TRAFFIC\x10\x022\xb7\f\n" + + "\rTOTAL_TRAFFIC\x10\x022\xf4\f\n" + "\x0eStartedService\x12=\n" + "\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" + "\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" + @@ -1767,7 +1813,8 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x13.daemon.Connections\"\x000\x01\x12K\n" + "\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" + "\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" + - "\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12J\n" + + "\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12;\n" + + "\fGetStartedAt\x12\x16.google.protobuf.Empty\x1a\x11.daemon.StartedAt\"\x00\x12J\n" + "\x15SubscribeHelperEvents\x12\x16.google.protobuf.Empty\x1a\x15.daemon.HelperRequest\"\x000\x01\x12F\n" + "\x12SendHelperResponse\x12\x16.daemon.HelperResponse\x1a\x16.google.protobuf.Empty\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" @@ -1785,7 +1832,7 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte { var ( file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) - file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 23) + file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 24) file_daemon_started_service_proto_goTypes = []any{ (LogLevel)(0), // 0: daemon.LogLevel (ConnectionFilter)(0), // 1: daemon.ConnectionFilter @@ -1813,16 +1860,17 @@ var ( (*CloseConnectionRequest)(nil), // 23: daemon.CloseConnectionRequest (*DeprecatedWarnings)(nil), // 24: daemon.DeprecatedWarnings (*DeprecatedWarning)(nil), // 25: daemon.DeprecatedWarning - (*Log_Message)(nil), // 26: daemon.Log.Message - (*emptypb.Empty)(nil), // 27: google.protobuf.Empty - (*HelperResponse)(nil), // 28: daemon.HelperResponse - (*HelperRequest)(nil), // 29: daemon.HelperRequest + (*StartedAt)(nil), // 26: daemon.StartedAt + (*Log_Message)(nil), // 27: daemon.Log.Message + (*emptypb.Empty)(nil), // 28: google.protobuf.Empty + (*HelperResponse)(nil), // 29: daemon.HelperResponse + (*HelperRequest)(nil), // 30: daemon.HelperRequest } ) var file_daemon_started_service_proto_depIdxs = []int32{ 3, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type - 26, // 1: daemon.Log.messages:type_name -> daemon.Log.Message + 27, // 1: daemon.Log.messages:type_name -> daemon.Log.Message 0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel 11, // 3: daemon.Groups.group:type_name -> daemon.Group 12, // 4: daemon.Group.items:type_name -> daemon.GroupItem @@ -1831,52 +1879,54 @@ var file_daemon_started_service_proto_depIdxs = []int32{ 22, // 7: daemon.Connections.connections:type_name -> daemon.Connection 25, // 8: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning 0, // 9: daemon.Log.Message.level:type_name -> daemon.LogLevel - 27, // 10: daemon.StartedService.StopService:input_type -> google.protobuf.Empty - 27, // 11: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty - 27, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty - 27, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty - 27, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty - 27, // 15: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty + 28, // 10: daemon.StartedService.StopService:input_type -> google.protobuf.Empty + 28, // 11: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty + 28, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty + 28, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty + 28, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty + 28, // 15: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty 6, // 16: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest - 27, // 17: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty - 27, // 18: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty - 27, // 19: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty + 28, // 17: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty + 28, // 18: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty + 28, // 19: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty 16, // 20: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode 13, // 21: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest 14, // 22: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest 15, // 23: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest - 27, // 24: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty + 28, // 24: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty 19, // 25: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest 20, // 26: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest 23, // 27: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest - 27, // 28: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty - 27, // 29: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty - 27, // 30: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty - 28, // 31: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse - 27, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty - 27, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty - 4, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus - 7, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log - 8, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel - 27, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty - 9, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status - 10, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups - 17, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus - 16, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode - 27, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty - 27, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty - 27, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty - 27, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty - 18, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus - 27, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty - 21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections - 27, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty - 27, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty - 24, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings - 29, // 52: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest - 27, // 53: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty - 32, // [32:54] is the sub-list for method output_type - 10, // [10:32] is the sub-list for method input_type + 28, // 28: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty + 28, // 29: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty + 28, // 30: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty + 28, // 31: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty + 29, // 32: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse + 28, // 33: daemon.StartedService.StopService:output_type -> google.protobuf.Empty + 28, // 34: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty + 4, // 35: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 7, // 36: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 8, // 37: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 28, // 38: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty + 9, // 39: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 10, // 40: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 17, // 41: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 16, // 42: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 28, // 43: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 28, // 44: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 28, // 45: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 28, // 46: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 18, // 47: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 28, // 48: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty + 21, // 49: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections + 28, // 50: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 28, // 51: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 24, // 52: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 26, // 53: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt + 30, // 54: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest + 28, // 55: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty + 33, // [33:56] is the sub-list for method output_type + 10, // [10:33] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name @@ -1894,7 +1944,7 @@ func file_daemon_started_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)), NumEnums: 4, - NumMessages: 23, + NumMessages: 24, NumExtensions: 0, NumServices: 1, }, diff --git a/daemon/started_service.proto b/daemon/started_service.proto index 83b72ff8..b44e2613 100644 --- a/daemon/started_service.proto +++ b/daemon/started_service.proto @@ -32,6 +32,7 @@ service StartedService { rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {} rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {} rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {} + rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {} rpc SubscribeHelperEvents(google.protobuf.Empty) returns(stream HelperRequest) {} rpc SendHelperResponse(HelperResponse) returns(google.protobuf.Empty) {} @@ -202,4 +203,8 @@ message DeprecatedWarning { string message = 1; bool impending = 2; string migrationLink = 3; +} + +message StartedAt { + int64 startedAt = 1; } \ No newline at end of file diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go index dec45dae..a9580469 100644 --- a/daemon/started_service_grpc.pb.go +++ b/daemon/started_service_grpc.pb.go @@ -35,6 +35,7 @@ const ( StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection" StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections" StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings" + StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt" StartedService_SubscribeHelperEvents_FullMethodName = "/daemon.StartedService/SubscribeHelperEvents" StartedService_SendHelperResponse_FullMethodName = "/daemon.StartedService/SendHelperResponse" ) @@ -63,6 +64,7 @@ type StartedServiceClient interface { CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) + GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) } @@ -329,6 +331,16 @@ func (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *em return out, nil } +func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(StartedAt) + err := c.cc.Invoke(ctx, StartedService_GetStartedAt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *startedServiceClient) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeHelperEvents_FullMethodName, cOpts...) @@ -382,6 +394,7 @@ type StartedServiceServer interface { CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) + GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) mustEmbedUnimplementedStartedServiceServer() @@ -474,6 +487,10 @@ func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented") } +func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStartedAt not implemented") +} + func (UnimplementedStartedServiceServer) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error { return status.Errorf(codes.Unimplemented, "method SubscribeHelperEvents not implemented") } @@ -820,6 +837,24 @@ func _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context. return interceptor(ctx, in, info, handler) } +func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StartedServiceServer).GetStartedAt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StartedService_GetStartedAt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StartedServiceServer).GetStartedAt(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _StartedService_SubscribeHelperEvents_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(emptypb.Empty) if err := stream.RecvMsg(m); err != nil { @@ -912,6 +947,10 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetDeprecatedWarnings", Handler: _StartedService_GetDeprecatedWarnings_Handler, }, + { + MethodName: "GetStartedAt", + Handler: _StartedService_GetStartedAt_Handler, + }, { MethodName: "SendHelperResponse", Handler: _StartedService_SendHelperResponse_Handler, diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 9885af6e..8b60332e 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -466,6 +466,19 @@ func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) { return newIterator(notes), nil } +func (c *CommandClient) GetStartedAt() (int64, error) { + client, err := c.getClientForCall() + if err != nil { + return 0, err + } + + startedAt, err := client.GetStartedAt(context.Background(), &emptypb.Empty{}) + if err != nil { + return 0, err + } + return startedAt.StartedAt, nil +} + func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { client, err := c.getClientForCall() if err != nil { From 494990f914a7b77ce854603e1efd4beca0cd5dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 27 Dec 2025 13:33:58 +0800 Subject: [PATCH 080/185] Update bypass action behavior for auto redirect --- adapter/router.go | 2 +- docs/configuration/route/rule_action.md | 14 +++---- docs/configuration/route/rule_action.zh.md | 10 ++--- docs/configuration/shared/pre-match.md | 11 ++++++ docs/configuration/shared/pre-match.zh.md | 10 +++++ option/rule_action.go | 3 -- protocol/tailscale/endpoint.go | 2 +- protocol/tun/inbound.go | 4 +- protocol/wireguard/endpoint.go | 2 +- route/route.go | 43 ++++++++++++++++++---- route/rule/rule_action.go | 3 ++ 11 files changed, 75 insertions(+), 29 deletions(-) diff --git a/adapter/router.go b/adapter/router.go index 3cece2ee..a0df7124 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -21,7 +21,7 @@ import ( type Router interface { Lifecycle ConnectionRouter - PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) + PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) ConnectionRouterEx RuleSet(tag string) (RuleSet, bool) Rules() []Rule diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 241233b9..523ffec2 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -62,19 +62,19 @@ See `route-options` fields below. } ``` -`bypass` routes connection to the specified outbound. +`bypass` bypasses sing-box at the kernel level for auto redirect connections in pre-match. -For tun connections in [pre-match](/configuration/shared/pre-match/), -the connection will bypass sing-box and connect directly at the kernel level. - -For non-tun connections and already established connections, the behavior is the same as `route`. +For non-auto-redirect connections and already established connections, +if `outbound` is specified, the behavior is the same as `route`; +otherwise, the rule will be skipped. #### outbound -==Required== - Tag of target outbound. +If not specified, the rule only matches in [pre-match](/configuration/shared/pre-match/) +from auto redirect, and will be skipped in other contexts. + #### route-options Fields See `route-options` fields below. diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index c944d1a8..16efb53a 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -58,18 +58,16 @@ icon: material/new-box } ``` -`bypass` 将连接路由到指定出站。 +`bypass` 在预匹配中为 auto redirect 连接在内核层面绕过 sing-box。 -对于[预匹配](/configuration/shared/pre-match/)中的 tun 连接,连接将在内核层面绕过 sing-box 直接连接。 - -对于非 tun 连接和已建立的连接,行为与 `route` 相同。 +对于非 auto redirect 连接和已建立的连接,如果指定了 `outbound`,行为与 `route` 相同;否则规则将被跳过。 #### outbound -==必填== - 目标出站的标签。 +如果未指定,规则仅在来自 auto redirect 的[预匹配](/configuration/shared/pre-match/)中匹配,在其他场景中将被跳过。 + #### route-options 字段 参阅下方的 `route-options` 字段。 diff --git a/docs/configuration/shared/pre-match.md b/docs/configuration/shared/pre-match.md index 180099aa..a0faf577 100644 --- a/docs/configuration/shared/pre-match.md +++ b/docs/configuration/shared/pre-match.md @@ -24,10 +24,14 @@ When a rule matches an action that requires an established connection, pre-match Reject with TCP RST / ICMP unreachable. +See [reject](/configuration/route/rule_action/#reject) for details. + #### route Route ICMP connections to the specified outbound for direct reply. +See [route](/configuration/route/rule_action/#route) for details. + #### bypass !!! question "Since sing-box 1.13.0" @@ -37,3 +41,10 @@ Route ICMP connections to the specified outbound for direct reply. Only supported on Linux with `auto_redirect` enabled. Bypass sing-box and connect directly at kernel level. + +If `outbound` is not specified, the rule only matches in pre-match from auto redirect, +and will be skipped in other contexts. + +For all other contexts, bypass with `outbound` behaves like `route` action. + +See [bypass](/configuration/route/rule_action/#bypass) for details. diff --git a/docs/configuration/shared/pre-match.zh.md b/docs/configuration/shared/pre-match.zh.md index c615070f..615400b0 100644 --- a/docs/configuration/shared/pre-match.zh.md +++ b/docs/configuration/shared/pre-match.zh.md @@ -22,10 +22,14 @@ icon: material/new-box 以 TCP RST / ICMP 不可达拒绝。 +详情参阅 [reject](/configuration/route/rule_action/#reject)。 + #### route 将 ICMP 连接路由到指定出站以直接回复。 +详情参阅 [route](/configuration/route/rule_action/#route)。 + #### bypass !!! question "自 sing-box 1.13.0 起" @@ -35,3 +39,9 @@ icon: material/new-box 仅支持 Linux,且需要启用 `auto_redirect`。 在内核层面绕过 sing-box 直接连接。 + +如果未指定 `outbound`,规则仅在来自 auto redirect 的预匹配中匹配,在其他场景中将被跳过。 + +对于其他所有场景,指定了 `outbound` 的 bypass 行为与 `route` 相同。 + +详情参阅 [bypass](/configuration/route/rule_action/#bypass)。 diff --git a/option/rule_action.go b/option/rule_action.go index 48326aed..43108255 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -93,9 +93,6 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { if err != nil { return err } - if r.Action == C.RuleActionTypeBypass && r.BypassOptions.Outbound == "" { - return E.New("missing outbound for bypass action") - } return nil } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index ca5be736..30cdd718 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -479,7 +479,7 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina Network: network, Source: source, Destination: destination, - }, routeContext, timeout) + }, routeContext, timeout, false) if err != nil { switch { case rule.IsBypassed(err): diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 43f6680c..2ab68b3e 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -480,7 +480,7 @@ func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destinat Source: source, Destination: destination, InboundOptions: t.inboundOptions, - }, routeContext, timeout) + }, routeContext, timeout, false) if err != nil { switch { case rule.IsBypassed(err): @@ -541,7 +541,7 @@ func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksad Source: source, Destination: destination, InboundOptions: t.inboundOptions, - }, routeContext, timeout) + }, routeContext, timeout, true) if err != nil { switch { case rule.IsBypassed(err): diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index a68d3b36..35ffd19e 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -140,7 +140,7 @@ func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina Network: network, Source: source, Destination: destination, - }, routeContext, timeout) + }, routeContext, timeout, false) if err != nil { switch { case rule.IsBypassed(err): diff --git a/route/route.go b/route/route.go index 2e4b854a..fd025a1b 100644 --- a/route/route.go +++ b/route/route.go @@ -95,7 +95,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad if deadline.NeedAdditionalReadDeadline(conn) { conn = deadline.NewConn(conn) } - selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, conn, nil) + selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, false, conn, nil) if err != nil { return err } @@ -114,6 +114,9 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) } case *R.RuleActionBypass: + if action.Outbound == "" { + break + } var loaded bool selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) if !loaded { @@ -223,7 +226,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m conn = deadline.NewPacketConn(bufio.NewNetPacketConn(conn)) }*/ - selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, nil, conn) + selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, false, nil, conn) if err != nil { return err } @@ -243,6 +246,9 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) } case *R.RuleActionBypass: + if action.Outbound == "" { + break + } var loaded bool selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) if !loaded { @@ -289,8 +295,8 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m return nil } -func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { - selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil) +func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) { + selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, supportBypass, nil, nil) if err != nil { return nil, err } @@ -310,7 +316,20 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire } return nil, action.Error(context.Background()) case *R.RuleActionBypass: - return nil, &R.BypassedError{Cause: tun.ErrBypass} + if supportBypass { + return nil, &R.BypassedError{Cause: tun.ErrBypass} + } + if routeContext == nil { + return nil, nil + } + outbound, loaded := r.outbound.Outbound(action.Outbound) + if !loaded { + return nil, E.New("outbound not found: ", action.Outbound) + } + if !common.Contains(outbound.Network(), metadata.Network) { + return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound) + } + directRouteOutbound = outbound.(adapter.DirectRouteOutbound) case *R.RuleActionRoute: if routeContext == nil { return nil, nil @@ -388,7 +407,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire } func (r *Router) matchRule( - ctx context.Context, metadata *adapter.InboundContext, preMatch bool, + ctx context.Context, metadata *adapter.InboundContext, preMatch bool, supportBypass bool, inputConn net.Conn, inputPacketConn N.PacketConn, ) ( selectedRule adapter.Rule, selectedRuleIndex int, @@ -591,8 +610,16 @@ match: actionType := currentRule.Action().Type() if actionType == C.RuleActionTypeRoute || actionType == C.RuleActionTypeReject || - actionType == C.RuleActionTypeHijackDNS || - actionType == C.RuleActionTypeBypass { + actionType == C.RuleActionTypeHijackDNS { + selectedRule = currentRule + selectedRuleIndex = currentRuleIndex + break match + } + if actionType == C.RuleActionTypeBypass { + bypassAction := currentRule.Action().(*R.RuleActionBypass) + if !supportBypass && bypassAction.Outbound == "" { + continue match + } selectedRule = currentRule selectedRuleIndex = currentRuleIndex break match diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index f4608e0a..cac814e7 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -183,6 +183,9 @@ func (r *RuleActionBypass) Type() string { } func (r *RuleActionBypass) String() string { + if r.Outbound == "" { + return "bypass()" + } var descriptions []string descriptions = append(descriptions, r.Outbound) descriptions = append(descriptions, r.Descriptions()...) From 4e94a64dcc675d50a76cad3cee324f350b9fe5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 27 Dec 2025 20:13:01 +0800 Subject: [PATCH 081/185] platform: Expose process info --- daemon/started_service.go | 11 ++ daemon/started_service.pb.go | 253 ++++++++++++++++++--------- daemon/started_service.proto | 9 + experimental/libbox/command_types.go | 20 +++ route/router.go | 3 + 5 files changed, 216 insertions(+), 80 deletions(-) diff --git a/daemon/started_service.go b/daemon/started_service.go index 4f147cf8..2eb46106 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -737,6 +737,16 @@ func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol uplink = 0 downlink = 0 } + var processInfo *ProcessInfo + if metadata.Metadata.ProcessInfo != nil { + processInfo = &ProcessInfo{ + ProcessId: metadata.Metadata.ProcessInfo.ProcessID, + UserId: metadata.Metadata.ProcessInfo.UserId, + UserName: metadata.Metadata.ProcessInfo.UserName, + ProcessPath: metadata.Metadata.ProcessInfo.ProcessPath, + PackageName: metadata.Metadata.ProcessInfo.AndroidPackageName, + } + } connection := &Connection{ Id: metadata.ID.String(), Inbound: metadata.Metadata.Inbound, @@ -759,6 +769,7 @@ func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol Outbound: metadata.Outbound, OutboundType: metadata.OutboundType, ChainList: metadata.Chain, + ProcessInfo: processInfo, } connections[metadata.ID] = connection return connection diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go index 3a820e6f..5f3726fe 100644 --- a/daemon/started_service.pb.go +++ b/daemon/started_service.pb.go @@ -1238,6 +1238,7 @@ type Connection struct { Outbound string `protobuf:"bytes,19,opt,name=outbound,proto3" json:"outbound,omitempty"` OutboundType string `protobuf:"bytes,20,opt,name=outboundType,proto3" json:"outboundType,omitempty"` ChainList []string `protobuf:"bytes,21,rep,name=chainList,proto3" json:"chainList,omitempty"` + ProcessInfo *ProcessInfo `protobuf:"bytes,22,opt,name=processInfo,proto3" json:"processInfo,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1419,6 +1420,89 @@ func (x *Connection) GetChainList() []string { return nil } +func (x *Connection) GetProcessInfo() *ProcessInfo { + if x != nil { + return x.ProcessInfo + } + return nil +} + +type ProcessInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProcessId uint32 `protobuf:"varint,1,opt,name=processId,proto3" json:"processId,omitempty"` + UserId int32 `protobuf:"varint,2,opt,name=userId,proto3" json:"userId,omitempty"` + UserName string `protobuf:"bytes,3,opt,name=userName,proto3" json:"userName,omitempty"` + ProcessPath string `protobuf:"bytes,4,opt,name=processPath,proto3" json:"processPath,omitempty"` + PackageName string `protobuf:"bytes,5,opt,name=packageName,proto3" json:"packageName,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProcessInfo) Reset() { + *x = ProcessInfo{} + mi := &file_daemon_started_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProcessInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessInfo) ProtoMessage() {} + +func (x *ProcessInfo) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead. +func (*ProcessInfo) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{19} +} + +func (x *ProcessInfo) GetProcessId() uint32 { + if x != nil { + return x.ProcessId + } + return 0 +} + +func (x *ProcessInfo) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ProcessInfo) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *ProcessInfo) GetProcessPath() string { + if x != nil { + return x.ProcessPath + } + return "" +} + +func (x *ProcessInfo) GetPackageName() string { + if x != nil { + return x.PackageName + } + return "" +} + type CloseConnectionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -1428,7 +1512,7 @@ type CloseConnectionRequest struct { func (x *CloseConnectionRequest) Reset() { *x = CloseConnectionRequest{} - mi := &file_daemon_started_service_proto_msgTypes[19] + mi := &file_daemon_started_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1440,7 +1524,7 @@ func (x *CloseConnectionRequest) String() string { func (*CloseConnectionRequest) ProtoMessage() {} func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[19] + mi := &file_daemon_started_service_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1453,7 +1537,7 @@ func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead. func (*CloseConnectionRequest) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{19} + return file_daemon_started_service_proto_rawDescGZIP(), []int{20} } func (x *CloseConnectionRequest) GetId() string { @@ -1472,7 +1556,7 @@ type DeprecatedWarnings struct { func (x *DeprecatedWarnings) Reset() { *x = DeprecatedWarnings{} - mi := &file_daemon_started_service_proto_msgTypes[20] + mi := &file_daemon_started_service_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1484,7 +1568,7 @@ func (x *DeprecatedWarnings) String() string { func (*DeprecatedWarnings) ProtoMessage() {} func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[20] + mi := &file_daemon_started_service_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1497,7 +1581,7 @@ func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message { // Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead. func (*DeprecatedWarnings) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{20} + return file_daemon_started_service_proto_rawDescGZIP(), []int{21} } func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning { @@ -1518,7 +1602,7 @@ type DeprecatedWarning struct { func (x *DeprecatedWarning) Reset() { *x = DeprecatedWarning{} - mi := &file_daemon_started_service_proto_msgTypes[21] + mi := &file_daemon_started_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1530,7 +1614,7 @@ func (x *DeprecatedWarning) String() string { func (*DeprecatedWarning) ProtoMessage() {} func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[21] + mi := &file_daemon_started_service_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1543,7 +1627,7 @@ func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message { // Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead. func (*DeprecatedWarning) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{21} + return file_daemon_started_service_proto_rawDescGZIP(), []int{22} } func (x *DeprecatedWarning) GetMessage() string { @@ -1576,7 +1660,7 @@ type StartedAt struct { func (x *StartedAt) Reset() { *x = StartedAt{} - mi := &file_daemon_started_service_proto_msgTypes[22] + mi := &file_daemon_started_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1588,7 +1672,7 @@ func (x *StartedAt) String() string { func (*StartedAt) ProtoMessage() {} func (x *StartedAt) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[22] + mi := &file_daemon_started_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1601,7 +1685,7 @@ func (x *StartedAt) ProtoReflect() protoreflect.Message { // Deprecated: Use StartedAt.ProtoReflect.Descriptor instead. func (*StartedAt) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{22} + return file_daemon_started_service_proto_rawDescGZIP(), []int{23} } func (x *StartedAt) GetStartedAt() int64 { @@ -1621,7 +1705,7 @@ type Log_Message struct { func (x *Log_Message) Reset() { *x = Log_Message{} - mi := &file_daemon_started_service_proto_msgTypes[23] + mi := &file_daemon_started_service_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1633,7 +1717,7 @@ func (x *Log_Message) String() string { func (*Log_Message) ProtoMessage() {} func (x *Log_Message) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[23] + mi := &file_daemon_started_service_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1740,7 +1824,7 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x06filter\x18\x02 \x01(\x0e2\x18.daemon.ConnectionFilterR\x06filter\x120\n" + "\x06sortBy\x18\x03 \x01(\x0e2\x18.daemon.ConnectionSortByR\x06sortBy\"C\n" + "\vConnections\x124\n" + - "\vconnections\x18\x01 \x03(\v2\x12.daemon.ConnectionR\vconnections\"\xde\x04\n" + + "\vconnections\x18\x01 \x03(\v2\x12.daemon.ConnectionR\vconnections\"\x95\x05\n" + "\n" + "Connection\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + @@ -1764,7 +1848,14 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x04rule\x18\x12 \x01(\tR\x04rule\x12\x1a\n" + "\boutbound\x18\x13 \x01(\tR\boutbound\x12\"\n" + "\foutboundType\x18\x14 \x01(\tR\foutboundType\x12\x1c\n" + - "\tchainList\x18\x15 \x03(\tR\tchainList\"(\n" + + "\tchainList\x18\x15 \x03(\tR\tchainList\x125\n" + + "\vprocessInfo\x18\x16 \x01(\v2\x13.daemon.ProcessInfoR\vprocessInfo\"\xa3\x01\n" + + "\vProcessInfo\x12\x1c\n" + + "\tprocessId\x18\x01 \x01(\rR\tprocessId\x12\x16\n" + + "\x06userId\x18\x02 \x01(\x05R\x06userId\x12\x1a\n" + + "\buserName\x18\x03 \x01(\tR\buserName\x12 \n" + + "\vprocessPath\x18\x04 \x01(\tR\vprocessPath\x12 \n" + + "\vpackageName\x18\x05 \x01(\tR\vpackageName\"(\n" + "\x16CloseConnectionRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\"K\n" + "\x12DeprecatedWarnings\x125\n" + @@ -1832,7 +1923,7 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte { var ( file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) - file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 24) + file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 25) file_daemon_started_service_proto_goTypes = []any{ (LogLevel)(0), // 0: daemon.LogLevel (ConnectionFilter)(0), // 1: daemon.ConnectionFilter @@ -1857,79 +1948,81 @@ var ( (*SubscribeConnectionsRequest)(nil), // 20: daemon.SubscribeConnectionsRequest (*Connections)(nil), // 21: daemon.Connections (*Connection)(nil), // 22: daemon.Connection - (*CloseConnectionRequest)(nil), // 23: daemon.CloseConnectionRequest - (*DeprecatedWarnings)(nil), // 24: daemon.DeprecatedWarnings - (*DeprecatedWarning)(nil), // 25: daemon.DeprecatedWarning - (*StartedAt)(nil), // 26: daemon.StartedAt - (*Log_Message)(nil), // 27: daemon.Log.Message - (*emptypb.Empty)(nil), // 28: google.protobuf.Empty - (*HelperResponse)(nil), // 29: daemon.HelperResponse - (*HelperRequest)(nil), // 30: daemon.HelperRequest + (*ProcessInfo)(nil), // 23: daemon.ProcessInfo + (*CloseConnectionRequest)(nil), // 24: daemon.CloseConnectionRequest + (*DeprecatedWarnings)(nil), // 25: daemon.DeprecatedWarnings + (*DeprecatedWarning)(nil), // 26: daemon.DeprecatedWarning + (*StartedAt)(nil), // 27: daemon.StartedAt + (*Log_Message)(nil), // 28: daemon.Log.Message + (*emptypb.Empty)(nil), // 29: google.protobuf.Empty + (*HelperResponse)(nil), // 30: daemon.HelperResponse + (*HelperRequest)(nil), // 31: daemon.HelperRequest } ) var file_daemon_started_service_proto_depIdxs = []int32{ 3, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type - 27, // 1: daemon.Log.messages:type_name -> daemon.Log.Message + 28, // 1: daemon.Log.messages:type_name -> daemon.Log.Message 0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel 11, // 3: daemon.Groups.group:type_name -> daemon.Group 12, // 4: daemon.Group.items:type_name -> daemon.GroupItem 1, // 5: daemon.SubscribeConnectionsRequest.filter:type_name -> daemon.ConnectionFilter 2, // 6: daemon.SubscribeConnectionsRequest.sortBy:type_name -> daemon.ConnectionSortBy 22, // 7: daemon.Connections.connections:type_name -> daemon.Connection - 25, // 8: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning - 0, // 9: daemon.Log.Message.level:type_name -> daemon.LogLevel - 28, // 10: daemon.StartedService.StopService:input_type -> google.protobuf.Empty - 28, // 11: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty - 28, // 12: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty - 28, // 13: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty - 28, // 14: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty - 28, // 15: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty - 6, // 16: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest - 28, // 17: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty - 28, // 18: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty - 28, // 19: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty - 16, // 20: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode - 13, // 21: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest - 14, // 22: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest - 15, // 23: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest - 28, // 24: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty - 19, // 25: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest - 20, // 26: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest - 23, // 27: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest - 28, // 28: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty - 28, // 29: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty - 28, // 30: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty - 28, // 31: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty - 29, // 32: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse - 28, // 33: daemon.StartedService.StopService:output_type -> google.protobuf.Empty - 28, // 34: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty - 4, // 35: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus - 7, // 36: daemon.StartedService.SubscribeLog:output_type -> daemon.Log - 8, // 37: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel - 28, // 38: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty - 9, // 39: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status - 10, // 40: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups - 17, // 41: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus - 16, // 42: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode - 28, // 43: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty - 28, // 44: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty - 28, // 45: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty - 28, // 46: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty - 18, // 47: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus - 28, // 48: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty - 21, // 49: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections - 28, // 50: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty - 28, // 51: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty - 24, // 52: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings - 26, // 53: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt - 30, // 54: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest - 28, // 55: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty - 33, // [33:56] is the sub-list for method output_type - 10, // [10:33] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo + 26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning + 0, // 10: daemon.Log.Message.level:type_name -> daemon.LogLevel + 29, // 11: daemon.StartedService.StopService:input_type -> google.protobuf.Empty + 29, // 12: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty + 29, // 13: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty + 29, // 14: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty + 29, // 15: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty + 29, // 16: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty + 6, // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest + 29, // 18: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty + 29, // 19: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty + 29, // 20: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty + 16, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode + 13, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest + 14, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest + 15, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest + 29, // 25: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty + 19, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest + 20, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest + 24, // 28: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest + 29, // 29: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty + 29, // 30: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty + 29, // 31: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty + 29, // 32: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty + 30, // 33: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse + 29, // 34: daemon.StartedService.StopService:output_type -> google.protobuf.Empty + 29, // 35: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty + 4, // 36: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 7, // 37: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 8, // 38: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 29, // 39: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty + 9, // 40: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 10, // 41: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 17, // 42: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 16, // 43: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 29, // 44: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 29, // 45: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 29, // 46: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 29, // 47: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 18, // 48: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 29, // 49: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty + 21, // 50: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections + 29, // 51: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 29, // 52: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 25, // 53: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 27, // 54: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt + 31, // 55: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest + 29, // 56: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty + 34, // [34:57] is the sub-list for method output_type + 11, // [11:34] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_daemon_started_service_proto_init() } @@ -1944,7 +2037,7 @@ func file_daemon_started_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)), NumEnums: 4, - NumMessages: 24, + NumMessages: 25, NumExtensions: 0, NumServices: 1, }, diff --git a/daemon/started_service.proto b/daemon/started_service.proto index b44e2613..a063d7a3 100644 --- a/daemon/started_service.proto +++ b/daemon/started_service.proto @@ -189,6 +189,15 @@ message Connection { string outbound = 19; string outboundType = 20; repeated string chainList = 21; + ProcessInfo processInfo = 22; +} + +message ProcessInfo { + uint32 processId = 1; + int32 userId = 2; + string userName = 3; + string processPath = 4; + string packageName = 5; } message CloseConnectionRequest { diff --git a/experimental/libbox/command_types.go b/experimental/libbox/command_types.go index 9383d04b..aa2fcdf2 100644 --- a/experimental/libbox/command_types.go +++ b/experimental/libbox/command_types.go @@ -130,6 +130,14 @@ func (c *Connections) Iterator() ConnectionIterator { return newPtrIterator(c.filtered) } +type ProcessInfo struct { + ProcessID int64 + UserID int32 + UserName string + ProcessPath string + PackageName string +} + type Connection struct { ID string Inbound string @@ -152,6 +160,7 @@ type Connection struct { Outbound string OutboundType string ChainList []string + ProcessInfo *ProcessInfo } func (c *Connection) Chain() StringIterator { @@ -219,6 +228,16 @@ func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator } func ConnectionFromGRPC(conn *daemon.Connection) Connection { + var processInfo *ProcessInfo + if conn.ProcessInfo != nil { + processInfo = &ProcessInfo{ + ProcessID: int64(conn.ProcessInfo.ProcessId), + UserID: conn.ProcessInfo.UserId, + UserName: conn.ProcessInfo.UserName, + ProcessPath: conn.ProcessInfo.ProcessPath, + PackageName: conn.ProcessInfo.PackageName, + } + } return Connection{ ID: conn.Id, Inbound: conn.Inbound, @@ -241,6 +260,7 @@ func ConnectionFromGRPC(conn *daemon.Connection) Connection { Outbound: conn.Outbound, OutboundType: conn.OutboundType, ChainList: conn.ChainList, + ProcessInfo: processInfo, } } diff --git a/route/router.go b/route/router.go index e3802dd8..fe72c1c5 100644 --- a/route/router.go +++ b/route/router.go @@ -118,6 +118,9 @@ func (r *Router) Start(stage adapter.StartStage) error { needFindProcess = true } } + if C.IsAndroid && r.platformInterface != nil { + needFindProcess = true + } if needFindProcess { if r.platformInterface != nil && r.platformInterface.UsePlatformConnectionOwnerFinder() { r.processSearcher = newPlatformSearcher(r.platformInterface) From aa8dd6e44fa7dd21066b7975951a106474e4f1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 29 Dec 2025 20:44:30 +0800 Subject: [PATCH 082/185] Fix DNS transports --- adapter/dns.go | 1 + dns/router.go | 2 +- dns/transport/base.go | 145 ++++++++++++++++ dns/transport/connector.go | 205 ++++++++++++++++++++++ dns/transport/dhcp/dhcp.go | 7 + dns/transport/fakeip/memory.go | 4 + dns/transport/fakeip/store.go | 38 ++-- dns/transport/hosts/hosts.go | 3 + dns/transport/https.go | 14 +- dns/transport/local/local.go | 3 + dns/transport/local/local_darwin.go | 6 + dns/transport/quic/http3.go | 71 ++++++-- dns/transport/quic/quic.go | 138 +++++++++------ dns/transport/tcp.go | 13 +- dns/transport/tls.go | 52 ++++-- dns/transport/udp.go | 259 +++++++++++++++------------- experimental/libbox/dns.go | 3 + service/resolved/transport.go | 10 ++ 18 files changed, 754 insertions(+), 220 deletions(-) create mode 100644 dns/transport/base.go create mode 100644 dns/transport/connector.go diff --git a/adapter/dns.go b/adapter/dns.go index bf73f4e5..8f065e2e 100644 --- a/adapter/dns.go +++ b/adapter/dns.go @@ -68,6 +68,7 @@ type DNSTransport interface { Type() string Tag() string Dependencies() []string + Reset() Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error) } diff --git a/dns/router.go b/dns/router.go index 1038fdf0..e82cab29 100644 --- a/dns/router.go +++ b/dns/router.go @@ -444,6 +444,6 @@ func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) { func (r *Router) ResetNetwork() { r.ClearCache() for _, transport := range r.transport.Transports() { - transport.Close() + transport.Reset() } } diff --git a/dns/transport/base.go b/dns/transport/base.go new file mode 100644 index 00000000..06e41fd0 --- /dev/null +++ b/dns/transport/base.go @@ -0,0 +1,145 @@ +package transport + +import ( + "context" + "os" + "sync" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +type TransportState int + +const ( + StateNew TransportState = iota + StateStarted + StateClosing + StateClosed +) + +var ( + ErrTransportClosed = os.ErrClosed + ErrConnectionReset = E.New("connection reset") +) + +type BaseTransport struct { + dns.TransportAdapter + Logger logger.ContextLogger + + mutex sync.Mutex + state TransportState + inFlight int32 + queriesComplete chan struct{} + closeCtx context.Context + closeCancel context.CancelFunc +} + +func NewBaseTransport(adapter dns.TransportAdapter, logger logger.ContextLogger) *BaseTransport { + ctx, cancel := context.WithCancel(context.Background()) + return &BaseTransport{ + TransportAdapter: adapter, + Logger: logger, + state: StateNew, + closeCtx: ctx, + closeCancel: cancel, + } +} + +func (t *BaseTransport) State() TransportState { + t.mutex.Lock() + defer t.mutex.Unlock() + return t.state +} + +func (t *BaseTransport) SetStarted() error { + t.mutex.Lock() + defer t.mutex.Unlock() + switch t.state { + case StateNew: + t.state = StateStarted + return nil + case StateStarted: + return nil + default: + return ErrTransportClosed + } +} + +func (t *BaseTransport) BeginQuery() bool { + t.mutex.Lock() + defer t.mutex.Unlock() + if t.state != StateStarted { + return false + } + t.inFlight++ + return true +} + +func (t *BaseTransport) EndQuery() { + t.mutex.Lock() + if t.inFlight > 0 { + t.inFlight-- + } + if t.inFlight == 0 && t.queriesComplete != nil { + close(t.queriesComplete) + t.queriesComplete = nil + } + t.mutex.Unlock() +} + +func (t *BaseTransport) CloseContext() context.Context { + return t.closeCtx +} + +func (t *BaseTransport) Shutdown(ctx context.Context) error { + t.mutex.Lock() + + if t.state >= StateClosing { + t.mutex.Unlock() + return nil + } + + if t.state == StateNew { + t.state = StateClosed + t.mutex.Unlock() + t.closeCancel() + return nil + } + + t.state = StateClosing + + if t.inFlight == 0 { + t.state = StateClosed + t.mutex.Unlock() + t.closeCancel() + return nil + } + + t.queriesComplete = make(chan struct{}) + queriesComplete := t.queriesComplete + t.mutex.Unlock() + + t.closeCancel() + + select { + case <-queriesComplete: + t.mutex.Lock() + t.state = StateClosed + t.mutex.Unlock() + return nil + case <-ctx.Done(): + t.mutex.Lock() + t.state = StateClosed + t.mutex.Unlock() + return ctx.Err() + } +} + +func (t *BaseTransport) Close() error { + ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout) + defer cancel() + return t.Shutdown(ctx) +} diff --git a/dns/transport/connector.go b/dns/transport/connector.go new file mode 100644 index 00000000..18fad0a5 --- /dev/null +++ b/dns/transport/connector.go @@ -0,0 +1,205 @@ +package transport + +import ( + "context" + "net" + "sync" +) + +type ConnectorCallbacks[T any] struct { + IsClosed func(connection T) bool + Close func(connection T) + Reset func(connection T) +} + +type Connector[T any] struct { + dial func(ctx context.Context) (T, error) + callbacks ConnectorCallbacks[T] + + access sync.Mutex + connection T + hasConnection bool + connecting chan struct{} + + closeCtx context.Context + closed bool +} + +func NewConnector[T any](closeCtx context.Context, dial func(context.Context) (T, error), callbacks ConnectorCallbacks[T]) *Connector[T] { + return &Connector[T]{ + dial: dial, + callbacks: callbacks, + closeCtx: closeCtx, + } +} + +func NewSingleflightConnector(closeCtx context.Context, dial func(context.Context) (*Connection, error)) *Connector[*Connection] { + return NewConnector(closeCtx, dial, ConnectorCallbacks[*Connection]{ + IsClosed: func(connection *Connection) bool { + return connection.IsClosed() + }, + Close: func(connection *Connection) { + connection.CloseWithError(ErrTransportClosed) + }, + Reset: func(connection *Connection) { + connection.CloseWithError(ErrConnectionReset) + }, + }) +} + +func (c *Connector[T]) Get(ctx context.Context) (T, error) { + var zero T + for { + c.access.Lock() + + if c.closed { + c.access.Unlock() + return zero, ErrTransportClosed + } + + if c.hasConnection && !c.callbacks.IsClosed(c.connection) { + connection := c.connection + c.access.Unlock() + return connection, nil + } + + c.hasConnection = false + + if c.connecting != nil { + connecting := c.connecting + c.access.Unlock() + + select { + case <-connecting: + continue + case <-ctx.Done(): + return zero, ctx.Err() + case <-c.closeCtx.Done(): + return zero, ErrTransportClosed + } + } + + c.connecting = make(chan struct{}) + c.access.Unlock() + + connection, err := c.dialWithCancellation(ctx) + + c.access.Lock() + close(c.connecting) + c.connecting = nil + + if err != nil { + c.access.Unlock() + return zero, err + } + + if c.closed { + c.callbacks.Close(connection) + c.access.Unlock() + return zero, ErrTransportClosed + } + + c.connection = connection + c.hasConnection = true + result := c.connection + c.access.Unlock() + + return result, nil + } +} + +func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, error) { + dialCtx, cancel := context.WithCancel(ctx) + defer cancel() + + go func() { + select { + case <-c.closeCtx.Done(): + cancel() + case <-dialCtx.Done(): + } + }() + + return c.dial(dialCtx) +} + +func (c *Connector[T]) Close() error { + c.access.Lock() + defer c.access.Unlock() + + if c.closed { + return nil + } + c.closed = true + + if c.hasConnection { + c.callbacks.Close(c.connection) + c.hasConnection = false + } + + return nil +} + +func (c *Connector[T]) Reset() { + c.access.Lock() + defer c.access.Unlock() + + if c.hasConnection { + c.callbacks.Reset(c.connection) + c.hasConnection = false + } +} + +type Connection struct { + net.Conn + + closeOnce sync.Once + done chan struct{} + closeError error +} + +func WrapConnection(conn net.Conn) *Connection { + return &Connection{ + Conn: conn, + done: make(chan struct{}), + } +} + +func (c *Connection) Done() <-chan struct{} { + return c.done +} + +func (c *Connection) IsClosed() bool { + select { + case <-c.done: + return true + default: + return false + } +} + +func (c *Connection) CloseError() error { + select { + case <-c.done: + if c.closeError != nil { + return c.closeError + } + return ErrTransportClosed + default: + return nil + } +} + +func (c *Connection) Close() error { + return c.CloseWithError(ErrTransportClosed) +} + +func (c *Connection) CloseWithError(err error) error { + var returnError error + c.closeOnce.Do(func() { + c.closeError = err + returnError = c.Conn.Close() + close(c.done) + }) + return returnError +} diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go index 3f13d1d9..3f4eb721 100644 --- a/dns/transport/dhcp/dhcp.go +++ b/dns/transport/dhcp/dhcp.go @@ -108,6 +108,13 @@ func (t *Transport) Close() error { return nil } +func (t *Transport) Reset() { + t.transportLock.Lock() + t.updatedAt = time.Time{} + t.servers = nil + t.transportLock.Unlock() +} + func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { servers, err := t.fetch() if err != nil { diff --git a/dns/transport/fakeip/memory.go b/dns/transport/fakeip/memory.go index 1640ab34..0cf8ecc7 100644 --- a/dns/transport/fakeip/memory.go +++ b/dns/transport/fakeip/memory.go @@ -82,8 +82,12 @@ func (s *MemoryStorage) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr } func (s *MemoryStorage) FakeIPReset() error { + s.addressAccess.Lock() + s.domainAccess.Lock() s.addressCache = make(map[netip.Addr]string) s.domainCache4 = make(map[string]netip.Addr) s.domainCache6 = make(map[string]netip.Addr) + s.domainAccess.Unlock() + s.addressAccess.Unlock() return nil } diff --git a/dns/transport/fakeip/store.go b/dns/transport/fakeip/store.go index 83677b0d..4c09ed7a 100644 --- a/dns/transport/fakeip/store.go +++ b/dns/transport/fakeip/store.go @@ -3,6 +3,7 @@ package fakeip import ( "context" "net/netip" + "sync" "github.com/sagernet/sing-box/adapter" E "github.com/sagernet/sing/common/exceptions" @@ -13,13 +14,15 @@ import ( var _ adapter.FakeIPStore = (*Store)(nil) type Store struct { - ctx context.Context - logger logger.Logger - inet4Range netip.Prefix - inet6Range netip.Prefix - storage adapter.FakeIPStorage - inet4Current netip.Addr - inet6Current netip.Addr + ctx context.Context + logger logger.Logger + inet4Range netip.Prefix + inet6Range netip.Prefix + storage adapter.FakeIPStorage + + addressAccess sync.Mutex + inet4Current netip.Addr + inet6Current netip.Addr } func NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store { @@ -65,18 +68,30 @@ func (s *Store) Close() error { if s.storage == nil { return nil } - return s.storage.FakeIPSaveMetadata(&adapter.FakeIPMetadata{ + s.addressAccess.Lock() + metadata := &adapter.FakeIPMetadata{ Inet4Range: s.inet4Range, Inet6Range: s.inet6Range, Inet4Current: s.inet4Current, Inet6Current: s.inet6Current, - }) + } + s.addressAccess.Unlock() + return s.storage.FakeIPSaveMetadata(metadata) } func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { if address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded { return address, nil } + + s.addressAccess.Lock() + defer s.addressAccess.Unlock() + + // Double-check after acquiring lock + if address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded { + return address, nil + } + var address netip.Addr if !isIPv6 { if !s.inet4Current.IsValid() { @@ -99,7 +114,10 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { s.inet6Current = nextAddress address = nextAddress } - s.storage.FakeIPStoreAsync(address, domain, s.logger) + err := s.storage.FakeIPStore(address, domain) + if err != nil { + s.logger.Warn("save FakeIP cache: ", err) + } s.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{ Inet4Range: s.inet4Range, Inet6Range: s.inet6Range, diff --git a/dns/transport/hosts/hosts.go b/dns/transport/hosts/hosts.go index a5eecb40..f0e70a9a 100644 --- a/dns/transport/hosts/hosts.go +++ b/dns/transport/hosts/hosts.go @@ -59,6 +59,9 @@ func (t *Transport) Close() error { return nil } +func (t *Transport) Reset() { +} + func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { question := message.Question[0] domain := mDNS.CanonicalName(question.Name) diff --git a/dns/transport/https.go b/dns/transport/https.go index 95fe7ed1..b508e6ea 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -145,6 +145,13 @@ func (t *HTTPSTransport) Close() error { return nil } +func (t *HTTPSTransport) Reset() { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() + t.transport.CloseIdleConnections() + t.transport = t.transport.Clone() +} + func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { startAt := time.Now() response, err := t.exchange(ctx, message) @@ -182,7 +189,10 @@ func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS request.Header = t.headers.Clone() request.Header.Set("Content-Type", MimeType) request.Header.Set("Accept", MimeType) - response, err := t.transport.RoundTrip(request) + t.transportAccess.Lock() + currentTransport := t.transport + t.transportAccess.Unlock() + response, err := currentTransport.RoundTrip(request) requestBuffer.Release() if err != nil { return nil, err @@ -194,12 +204,12 @@ func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS var responseMessage mDNS.Msg if response.ContentLength > 0 { responseBuffer := buf.NewSize(int(response.ContentLength)) + defer responseBuffer.Release() _, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength)) if err != nil { return nil, err } err = responseMessage.Unpack(responseBuffer.Bytes()) - responseBuffer.Release() } else { rawMessage, err = io.ReadAll(response.Body) if err != nil { diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index 51b8c18c..a42abc76 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -76,6 +76,9 @@ func (t *Transport) Close() error { return nil } +func (t *Transport) Reset() { +} + func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { if t.resolved != nil { resolverObject := t.resolved.Object() diff --git a/dns/transport/local/local_darwin.go b/dns/transport/local/local_darwin.go index ee759b91..5f1e60b1 100644 --- a/dns/transport/local/local_darwin.go +++ b/dns/transport/local/local_darwin.go @@ -92,6 +92,12 @@ func (t *Transport) Close() error { ) } +func (t *Transport) Reset() { + if t.dhcpTransport != nil { + t.dhcpTransport.Reset() + } +} + func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { question := message.Question[0] if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA { diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index 0459d685..c3a5ca81 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -8,10 +8,12 @@ import ( "net/http" "net/url" "strconv" + "sync" "github.com/sagernet/quic-go" "github.com/sagernet/quic-go/http3" "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/dns" @@ -23,6 +25,7 @@ import ( "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" sHTTP "github.com/sagernet/sing/protocol/http" @@ -37,11 +40,14 @@ func RegisterHTTP3Transport(registry *dns.TransportRegistry) { type HTTP3Transport struct { dns.TransportAdapter - logger logger.ContextLogger - dialer N.Dialer - destination *url.URL - headers http.Header - transport *http3.Transport + logger logger.ContextLogger + dialer N.Dialer + destination *url.URL + headers http.Header + serverAddr M.Socksaddr + tlsConfig *tls.STDConfig + transportAccess sync.Mutex + transport *http3.Transport } func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { @@ -95,33 +101,57 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options if !serverAddr.IsValid() { return nil, E.New("invalid server address: ", serverAddr) } - return &HTTP3Transport{ + t := &HTTP3Transport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions), logger: logger, dialer: transportDialer, destination: &destinationURL, headers: headers, - transport: &http3.Transport{ - Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (*quic.Conn, error) { - conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, serverAddr) - if dialErr != nil { - return nil, dialErr - } - return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(conn), conn.RemoteAddr(), tlsCfg, cfg) - }, - TLSClientConfig: stdConfig, + serverAddr: serverAddr, + tlsConfig: stdConfig, + } + t.transport = t.newTransport() + return t, nil +} + +func (t *HTTP3Transport) newTransport() *http3.Transport { + return &http3.Transport{ + Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (*quic.Conn, error) { + conn, dialErr := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) + if dialErr != nil { + return nil, dialErr + } + quicConn, dialErr := quic.DialEarly(ctx, bufio.NewUnbindPacketConn(conn), conn.RemoteAddr(), tlsCfg, cfg) + if dialErr != nil { + conn.Close() + return nil, dialErr + } + return quicConn, nil }, - }, nil + TLSClientConfig: t.tlsConfig, + } } func (t *HTTP3Transport) Start(stage adapter.StartStage) error { - return nil + if stage != adapter.StartStateStart { + return nil + } + return dialer.InitializeDetour(t.dialer) } func (t *HTTP3Transport) Close() error { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() return t.transport.Close() } +func (t *HTTP3Transport) Reset() { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() + t.transport.Close() + t.transport = t.newTransport() +} + func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { exMessage := *message exMessage.Id = 0 @@ -140,7 +170,10 @@ func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS request.Header = t.headers.Clone() request.Header.Set("Content-Type", transport.MimeType) request.Header.Set("Accept", transport.MimeType) - response, err := t.transport.RoundTrip(request) + t.transportAccess.Lock() + currentTransport := t.transport + t.transportAccess.Unlock() + response, err := currentTransport.RoundTrip(request) requestBuffer.Release() if err != nil { return nil, err @@ -152,12 +185,12 @@ func (t *HTTP3Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS var responseMessage mDNS.Msg if response.ContentLength > 0 { responseBuffer := buf.NewSize(int(response.ContentLength)) + defer responseBuffer.Release() _, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength)) if err != nil { return nil, err } err = responseMessage.Unpack(responseBuffer.Bytes()) - responseBuffer.Release() } else { rawMessage, err = io.ReadAll(response.Body) if err != nil { diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index a54cddcb..26461006 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -3,10 +3,11 @@ package quic import ( "context" "errors" - "sync" + "os" "github.com/sagernet/quic-go" "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/dns" @@ -17,7 +18,6 @@ import ( "github.com/sagernet/sing/common" "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" @@ -31,14 +31,14 @@ func RegisterTransport(registry *dns.TransportRegistry) { } type Transport struct { - dns.TransportAdapter + *transport.BaseTransport + ctx context.Context - logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr tlsConfig tls.Config - access sync.Mutex - connection *quic.Conn + + connector *transport.Connector[*quic.Conn] } func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) { @@ -62,38 +62,84 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options if !serverAddr.IsValid() { return nil, E.New("invalid server address: ", serverAddr) } - return &Transport{ - TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions), - ctx: ctx, - logger: logger, - dialer: transportDialer, - serverAddr: serverAddr, - tlsConfig: tlsConfig, - }, nil + + t := &Transport{ + BaseTransport: transport.NewBaseTransport( + dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions), + logger, + ), + ctx: ctx, + dialer: transportDialer, + serverAddr: serverAddr, + tlsConfig: tlsConfig, + } + + t.connector = transport.NewConnector(t.CloseContext(), t.dial, transport.ConnectorCallbacks[*quic.Conn]{ + IsClosed: func(connection *quic.Conn) bool { + return common.Done(connection.Context()) + }, + Close: func(connection *quic.Conn) { + connection.CloseWithError(0, "") + }, + Reset: func(connection *quic.Conn) { + connection.CloseWithError(0, "") + }, + }) + + return t, nil +} + +func (t *Transport) dial(ctx context.Context) (*quic.Conn, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial UDP connection") + } + earlyConnection, err := sQUIC.DialEarly( + ctx, + bufio.NewUnbindPacketConn(conn), + t.serverAddr.UDPAddr(), + t.tlsConfig, + nil, + ) + if err != nil { + conn.Close() + return nil, E.Cause(err, "establish QUIC connection") + } + return earlyConnection, nil } func (t *Transport) Start(stage adapter.StartStage) error { - return nil + if stage != adapter.StartStateStart { + return nil + } + err := t.SetStarted() + if err != nil { + return err + } + return dialer.InitializeDetour(t.dialer) } func (t *Transport) Close() error { - t.access.Lock() - defer t.access.Unlock() - connection := t.connection - if connection != nil { - connection.CloseWithError(0, "") - } - return nil + return E.Errors(t.BaseTransport.Close(), t.connector.Close()) +} + +func (t *Transport) Reset() { + t.connector.Reset() } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if !t.BeginQuery() { + return nil, transport.ErrTransportClosed + } + defer t.EndQuery() + var ( conn *quic.Conn err error response *mDNS.Msg ) for i := 0; i < 2; i++ { - conn, err = t.openConnection() + conn, err = t.connector.Get(ctx) if err != nil { return nil, err } @@ -103,58 +149,38 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, } else if !isQUICRetryError(err) { return nil, err } else { - conn.CloseWithError(quic.ApplicationErrorCode(0), "") + t.connector.Reset() continue } } return nil, err } -func (t *Transport) openConnection() (*quic.Conn, error) { - connection := t.connection - if connection != nil && !common.Done(connection.Context()) { - return connection, nil - } - t.access.Lock() - defer t.access.Unlock() - connection = t.connection - if connection != nil && !common.Done(connection.Context()) { - return connection, nil - } - conn, err := t.dialer.DialContext(t.ctx, N.NetworkUDP, t.serverAddr) - if err != nil { - return nil, err - } - earlyConnection, err := sQUIC.DialEarly( - t.ctx, - bufio.NewUnbindPacketConn(conn), - t.serverAddr.UDPAddr(), - t.tlsConfig, - nil, - ) - if err != nil { - return nil, err - } - t.connection = earlyConnection - return earlyConnection, nil -} - func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.Conn) (*mDNS.Msg, error) { stream, err := conn.OpenStreamSync(ctx) if err != nil { - return nil, err + return nil, E.Cause(err, "open stream") } + defer stream.CancelRead(0) err = transport.WriteMessage(stream, 0, message) if err != nil { stream.Close() - return nil, err + return nil, E.Cause(err, "write request") } stream.Close() - return transport.ReadMessage(stream) + response, err := transport.ReadMessage(stream) + if err != nil { + return nil, E.Cause(err, "read response") + } + return response, nil } // https://github.com/AdguardTeam/dnsproxy/blob/fd1868577652c639cce3da00e12ca548f421baf1/upstream/upstream_quic.go#L394 func isQUICRetryError(err error) (ok bool) { + if errors.Is(err, os.ErrClosed) { + return true + } + var qAppErr *quic.ApplicationError if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 { return true diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go index 3039c574..59333de8 100644 --- a/dns/transport/tcp.go +++ b/dns/transport/tcp.go @@ -62,17 +62,24 @@ func (t *TCPTransport) Close() error { return nil } +func (t *TCPTransport) Reset() { +} + func (t *TCPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr) if err != nil { - return nil, err + return nil, E.Cause(err, "dial TCP connection") } defer conn.Close() err = WriteMessage(conn, 0, message) if err != nil { - return nil, err + return nil, E.Cause(err, "write request") } - return ReadMessage(conn) + response, err := ReadMessage(conn) + if err != nil { + return nil, E.Cause(err, "read response") + } + return response, nil } func ReadMessage(reader io.Reader) (*mDNS.Msg, error) { diff --git a/dns/transport/tls.go b/dns/transport/tls.go index 932a72a8..4d463296 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -3,6 +3,7 @@ package transport import ( "context" "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" @@ -28,8 +29,8 @@ func RegisterTLS(registry *dns.TransportRegistry) { } type TLSTransport struct { - dns.TransportAdapter - logger logger.ContextLogger + *BaseTransport + dialer tls.Dialer serverAddr M.Socksaddr tlsConfig tls.Config @@ -65,11 +66,10 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport { return &TLSTransport{ - TransportAdapter: adapter, - logger: logger, - dialer: tls.NewDialer(dialer, tlsConfig), - serverAddr: serverAddr, - tlsConfig: tlsConfig, + BaseTransport: NewBaseTransport(adapter, logger), + dialer: tls.NewDialer(dialer, tlsConfig), + serverAddr: serverAddr, + tlsConfig: tlsConfig, } } @@ -77,37 +77,59 @@ func (t *TLSTransport) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } + err := t.SetStarted() + if err != nil { + return err + } return dialer.InitializeDetour(t.dialer) } func (t *TLSTransport) Close() error { + t.access.Lock() + for connection := t.connections.Front(); connection != nil; connection = connection.Next() { + connection.Value.Close() + } + t.connections.Init() + t.access.Unlock() + return t.BaseTransport.Close() +} + +func (t *TLSTransport) Reset() { t.access.Lock() defer t.access.Unlock() for connection := t.connections.Front(); connection != nil; connection = connection.Next() { connection.Value.Close() } t.connections.Init() - return nil } func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if !t.BeginQuery() { + return nil, ErrTransportClosed + } + defer t.EndQuery() + t.access.Lock() conn := t.connections.PopFront() t.access.Unlock() if conn != nil { - response, err := t.exchange(message, conn) + response, err := t.exchange(ctx, message, conn) if err == nil { return response, nil } + t.Logger.DebugContext(ctx, "discarded pooled connection: ", err) } tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr) if err != nil { - return nil, err + return nil, E.Cause(err, "dial TLS connection") } - return t.exchange(message, &tlsDNSConn{Conn: tlsConn}) + return t.exchange(ctx, message, &tlsDNSConn{Conn: tlsConn}) } -func (t *TLSTransport) exchange(message *mDNS.Msg, conn *tlsDNSConn) (*mDNS.Msg, error) { +func (t *TLSTransport) exchange(ctx context.Context, message *mDNS.Msg, conn *tlsDNSConn) (*mDNS.Msg, error) { + if deadline, ok := ctx.Deadline(); ok { + conn.SetDeadline(deadline) + } conn.queryId++ err := WriteMessage(conn, conn.queryId, message) if err != nil { @@ -120,6 +142,12 @@ func (t *TLSTransport) exchange(message *mDNS.Msg, conn *tlsDNSConn) (*mDNS.Msg, return nil, E.Cause(err, "read response") } t.access.Lock() + if t.State() >= StateClosing { + t.access.Unlock() + conn.Close() + return response, nil + } + conn.SetDeadline(time.Time{}) t.connections.PushBack(conn) t.access.Unlock() return response, nil diff --git a/dns/transport/udp.go b/dns/transport/udp.go index 48924c65..a7272545 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -2,9 +2,8 @@ package transport import ( "context" - "net" - "os" "sync" + "sync/atomic" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" @@ -28,15 +27,23 @@ func RegisterUDP(registry *dns.TransportRegistry) { } type UDPTransport struct { - dns.TransportAdapter - logger logger.ContextLogger - dialer N.Dialer - serverAddr M.Socksaddr - udpSize int - tcpTransport *TCPTransport - access sync.Mutex - conn *dnsConnection - done chan struct{} + *BaseTransport + + dialer N.Dialer + serverAddr M.Socksaddr + udpSize atomic.Int32 + + connector *Connector[*Connection] + + callbackAccess sync.RWMutex + queryId uint16 + callbacks map[uint16]*udpCallback +} + +type udpCallback struct { + access sync.Mutex + response *mDNS.Msg + done chan struct{} } func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteDNSServerOptions) (adapter.DNSTransport, error) { @@ -54,180 +61,198 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil } -func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr) *UDPTransport { - return &UDPTransport{ - TransportAdapter: adapter, - logger: logger, - dialer: dialer, - serverAddr: serverAddr, - udpSize: 2048, - tcpTransport: &TCPTransport{ - dialer: dialer, - serverAddr: serverAddr, - }, - done: make(chan struct{}), +func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialerInstance N.Dialer, serverAddr M.Socksaddr) *UDPTransport { + t := &UDPTransport{ + BaseTransport: NewBaseTransport(adapter, logger), + dialer: dialerInstance, + serverAddr: serverAddr, + callbacks: make(map[uint16]*udpCallback), } + t.udpSize.Store(2048) + t.connector = NewSingleflightConnector(t.CloseContext(), t.dial) + return t +} + +func (t *UDPTransport) dial(ctx context.Context) (*Connection, error) { + rawConn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial UDP connection") + } + conn := WrapConnection(rawConn) + go t.recvLoop(conn) + return conn, nil } func (t *UDPTransport) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } + err := t.SetStarted() + if err != nil { + return err + } return dialer.InitializeDetour(t.dialer) } func (t *UDPTransport) Close() error { - t.access.Lock() - defer t.access.Unlock() - close(t.done) - t.done = make(chan struct{}) - return nil + return E.Errors(t.BaseTransport.Close(), t.connector.Close()) +} + +func (t *UDPTransport) Reset() { + t.connector.Reset() +} + +func (t *UDPTransport) nextAvailableQueryId() (uint16, error) { + start := t.queryId + for { + t.queryId++ + if _, exists := t.callbacks[t.queryId]; !exists { + return t.queryId, nil + } + if t.queryId == start { + return 0, E.New("no available query ID") + } + } } func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + if !t.BeginQuery() { + return nil, ErrTransportClosed + } + defer t.EndQuery() + response, err := t.exchange(ctx, message) if err != nil { return nil, err } if response.Truncated { - t.logger.InfoContext(ctx, "response truncated, retrying with TCP") - return t.tcpTransport.Exchange(ctx, message) + t.Logger.InfoContext(ctx, "response truncated, retrying with TCP") + return t.exchangeTCP(ctx, message) + } + return response, nil +} + +func (t *UDPTransport) exchangeTCP(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr) + if err != nil { + return nil, E.Cause(err, "dial TCP connection") + } + defer conn.Close() + err = WriteMessage(conn, message.Id, message) + if err != nil { + return nil, E.Cause(err, "write request") + } + response, err := ReadMessage(conn) + if err != nil { + return nil, E.Cause(err, "read response") } return response, nil } func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - t.access.Lock() if edns0Opt := message.IsEdns0(); edns0Opt != nil { - if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize { - t.udpSize = udpSize - close(t.done) - t.done = make(chan struct{}) + udpSize := int32(edns0Opt.UDPSize()) + for { + current := t.udpSize.Load() + if udpSize <= current { + break + } + if t.udpSize.CompareAndSwap(current, udpSize) { + t.connector.Reset() + break + } } } - t.access.Unlock() - conn, err := t.open(ctx) + + conn, err := t.connector.Get(ctx) if err != nil { return nil, err } - buffer := buf.NewSize(1 + message.Len()) - defer buffer.Release() - exMessage := *message - exMessage.Compress = true - messageId := message.Id - callback := &dnsCallback{ + + callback := &udpCallback{ done: make(chan struct{}), } - conn.access.Lock() - conn.queryId++ - exMessage.Id = conn.queryId - conn.callbacks[exMessage.Id] = callback - conn.access.Unlock() + + t.callbackAccess.Lock() + queryId, err := t.nextAvailableQueryId() + if err != nil { + t.callbackAccess.Unlock() + return nil, err + } + t.callbacks[queryId] = callback + t.callbackAccess.Unlock() + defer func() { - conn.access.Lock() - delete(conn.callbacks, exMessage.Id) - conn.access.Unlock() + t.callbackAccess.Lock() + delete(t.callbacks, queryId) + t.callbackAccess.Unlock() }() + + buffer := buf.NewSize(1 + message.Len()) + defer buffer.Release() + + exMessage := *message + exMessage.Compress = true + originalId := message.Id + exMessage.Id = queryId + rawMessage, err := exMessage.PackBuffer(buffer.FreeBytes()) if err != nil { return nil, err } + _, err = conn.Write(rawMessage) if err != nil { - conn.Close(err) - return nil, err + conn.CloseWithError(err) + return nil, E.Cause(err, "write request") } + select { case <-callback.done: - callback.message.Id = messageId - return callback.message, nil - case <-conn.done: - return nil, conn.err - case <-t.done: - return nil, os.ErrClosed + callback.response.Id = originalId + return callback.response, nil + case <-conn.Done(): + return nil, conn.CloseError() + case <-t.CloseContext().Done(): + return nil, ErrTransportClosed case <-ctx.Done(): - conn.Close(ctx.Err()) return nil, ctx.Err() } } -func (t *UDPTransport) open(ctx context.Context) (*dnsConnection, error) { - t.access.Lock() - defer t.access.Unlock() - if t.conn != nil { - select { - case <-t.conn.done: - default: - return t.conn, nil - } - } - conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr) - if err != nil { - return nil, err - } - dnsConn := &dnsConnection{ - Conn: conn, - done: make(chan struct{}), - callbacks: make(map[uint16]*dnsCallback), - } - go t.recvLoop(dnsConn) - t.conn = dnsConn - return dnsConn, nil -} - -func (t *UDPTransport) recvLoop(conn *dnsConnection) { +func (t *UDPTransport) recvLoop(conn *Connection) { for { - buffer := buf.NewSize(t.udpSize) + buffer := buf.NewSize(int(t.udpSize.Load())) _, err := buffer.ReadOnceFrom(conn) if err != nil { buffer.Release() - conn.Close(err) + conn.CloseWithError(err) return } + var message mDNS.Msg err = message.Unpack(buffer.Bytes()) buffer.Release() if err != nil { - conn.Close(err) - return + t.Logger.Debug("discarded malformed UDP response: ", err) + continue } - conn.access.RLock() - callback, loaded := conn.callbacks[message.Id] - conn.access.RUnlock() + + t.callbackAccess.RLock() + callback, loaded := t.callbacks[message.Id] + t.callbackAccess.RUnlock() + if !loaded { continue } + callback.access.Lock() select { case <-callback.done: default: - callback.message = &message + callback.response = &message close(callback.done) } callback.access.Unlock() } } - -type dnsConnection struct { - net.Conn - access sync.RWMutex - done chan struct{} - closeOnce sync.Once - err error - queryId uint16 - callbacks map[uint16]*dnsCallback -} - -func (c *dnsConnection) Close(err error) { - c.closeOnce.Do(func() { - c.err = err - close(c.done) - }) - c.Conn.Close() -} - -type dnsCallback struct { - access sync.Mutex - message *mDNS.Msg - done chan struct{} -} diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index d5c97b7e..b7b3b0f6 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -46,6 +46,9 @@ func (p *platformTransport) Close() error { return nil } +func (p *platformTransport) Reset() { +} + func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { response := &ExchangeContext{ context: ctx, diff --git a/service/resolved/transport.go b/service/resolved/transport.go index c54a6341..ac20663a 100644 --- a/service/resolved/transport.go +++ b/service/resolved/transport.go @@ -110,6 +110,16 @@ func (t *Transport) Close() error { return nil } +func (t *Transport) Reset() { + t.linkAccess.RLock() + defer t.linkAccess.RUnlock() + for _, servers := range t.linkServers { + for _, server := range servers.Servers { + server.Reset() + } + } +} + func (t *Transport) updateTransports(link *TransportLink) error { t.linkAccess.Lock() defer t.linkAccess.Unlock() From 46c2cc37c309c62c565cc55ec626fd63c6ac2048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 30 Dec 2025 16:43:28 +0800 Subject: [PATCH 083/185] cronet: Fix windows DNS hijack --- .github/CRONET_GO_VERSION | 2 +- go.mod | 48 ++++++++++---------- go.sum | 96 +++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 84a69fd0..f987b703 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -ced05691cdd4e758286db059830ff034807da687 +1cc61ad20399081362ccbc18d650432d1a6d42ec diff --git a/go.mod b/go.mod index ff22d815..4da4a808 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251225133447-a96cfe4f6a69 - github.com/sagernet/cronet-go/all v0.0.0-20251225133447-a96cfe4f6a69 + github.com/sagernet/cronet-go v0.0.0-20251230094758-5aef2a9e7f0d + github.com/sagernet/cronet-go/all v0.0.0-20251230094758-5aef2a9e7f0d github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -102,28 +102,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251225132957-e1f82dabebce // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251225132957-e1f82dabebce // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index 36cbc7c9..3dd6f905 100644 --- a/go.sum +++ b/go.sum @@ -144,54 +144,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251225133447-a96cfe4f6a69 h1:5K8s47TvmoS6YhpuOu8cUx1fiyu/gzGMnp4s/56eBPg= -github.com/sagernet/cronet-go v0.0.0-20251225133447-a96cfe4f6a69/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251225133447-a96cfe4f6a69 h1:noutQedWtKA4Ry5HxAC0egq1/jPXhQLNGOhSBTvya+k= -github.com/sagernet/cronet-go/all v0.0.0-20251225133447-a96cfe4f6a69/go.mod h1:u8a+w3gHb1QMe5f3GeF6doqIb31ih4AvBCUiBMWK5Bg= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251225132957-e1f82dabebce h1:CyXJpJcFo2AFAq5hNDB1OeBRK/KR8CCeGPVwNXLaqi0= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251225132957-e1f82dabebce h1:awEG+TXzD4znoMCuTTJeMVyk9aH1tlabPOvu8cpnCWA= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251225132957-e1f82dabebce h1:FYYqebFnu0O3hl2N/ocq3ihQhSLfghFwubufRmQ0JQs= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251225132957-e1f82dabebce/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251225132957-e1f82dabebce h1:DWRUyY8Qdvso655bwlQxsIcIZoX1LdWkua4dVCJAKm8= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251225132957-e1f82dabebce h1:BIfMCrlftqJ05lQ6fmreS6fMqh+d31c+IRF4SEBP0EY= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251225132957-e1f82dabebce h1:S9kEuMdmx9QDoyCvBNmSs1wlV8KvdIYEdRu0tjeVnZQ= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251225132957-e1f82dabebce h1:EBNErcQd9OPNKUQ9Jo46GA1uzNZ8RZNFaBkowTlfcSU= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251225132957-e1f82dabebce h1:ySs/dm/1beLRapdP7O1nkex2vCNhttOwz0lnicWdAmU= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251225132957-e1f82dabebce h1:XiBQoMqRJnHNNBss517YO6YVtpe0Ib2JbrtApbHvl6k= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251225132957-e1f82dabebce h1:JpqQvxgAH8Eb1IAOu2nj/DjDqEyGhribM+wQXNW2/vE= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251225132957-e1f82dabebce h1:+SrPJfEs+Pp24O7agLam0q3DMgB64KCxAybsIGW1EUI= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251225132957-e1f82dabebce h1:i/sy1n46uteSLFnfGAchey68xTvZuxXzF+Qtn9X9ABk= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251225132957-e1f82dabebce h1:D0BPW5r0GphBcFfXMcQeD/9DCs1Y0PFpW2cHZ7ec7jc= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251225132957-e1f82dabebce h1:qO+gLXeZmqVIki2mbtJnjUNhDbaAJB4t0Mb8YNO+ySM= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251225132957-e1f82dabebce/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251225132957-e1f82dabebce h1:wthfnFHICUX687wgrL6YL1LYHJrfATWUz76jRIZ7EA0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251225132957-e1f82dabebce h1:gQMi9YHQvAMq5yzXpoWZ7aCCtnBVvVryCnpONc6Xvtg= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251225132957-e1f82dabebce h1:NkzbN5KmVJLbXkenOTeiCBOLJQBi3vBlKYFtuQ4nK4w= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251225132957-e1f82dabebce/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251225132957-e1f82dabebce h1:a76cCOcshqixwueb56hnGcUyyad/2M34nxTgLDa8xo4= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251225132957-e1f82dabebce h1:OU2WMFXlC2IsBHi2xrLWROa3Pw2dNBJ5RAwIFBNg9lM= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251225132957-e1f82dabebce h1:YCmnoOcnZtaQuoy9YX0AKhXCuDYYn21hrNa7sMkjM7k= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251225132957-e1f82dabebce/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251225132957-e1f82dabebce h1:a1qP/Kr90j9rHzsw1aOe2o9auDey9GbyJchh99xWMRc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251225132957-e1f82dabebce h1:q5AKReJPyQndi9+/fYVTx7Jgoy8Q+6lHVnbzoiPauQE= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251225132957-e1f82dabebce/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251230094758-5aef2a9e7f0d h1:1oTc5+PjhCjdDro0VYZzwPee0CvTmMbDnyxZAThYvq4= +github.com/sagernet/cronet-go v0.0.0-20251230094758-5aef2a9e7f0d/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251230094758-5aef2a9e7f0d h1:lRwTaavH9XOCf3QPEY9FNkOsy1jLWpo5Lg6Vas9XShs= +github.com/sagernet/cronet-go/all v0.0.0-20251230094758-5aef2a9e7f0d/go.mod h1:Wqam5NDbMkewyy+33wu6wsq4uNHGoskHaon1XsTxQMg= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251230094225-9a5fe902f561 h1:vjswtG+1CNj4kni3haVLzIn8C7RKa3RnFUXG8OofgSE= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:ciuXFp02usTUyj9MpzwlB0bEyEjF0VVINMl5QG5uIu4= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251230094225-9a5fe902f561 h1:qJVWgBiznBwkfQ9EB0TpjgVVeBVHb/COaItekq/EZ5w= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251230094225-9a5fe902f561/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:FEJJEbbmmOv37Lx+JgHlXIdTXzP0TNYHBvzGgbft4fA= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:w9Z3W30bdQrmme2qfJBfkZGUKirPe6N4caCDqp7+ArI= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:7lJXCswd6nIrw31MJYet0sNcIxD9MVxKW/UoSPynBxw= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:dCten+Kq71YCOGxezyIQqpe5zctf4Oxwhk8tgHUuy0g= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:Q6RE/v/XXaCLOh7w7iIuzCRhwWIjk+ikMcKSqO8vA6c= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:bzXuJwzMy/+RLVXYQrNL3cqPYP7pW9OaItF0zyNkBl4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251230094225-9a5fe902f561 h1:bzYwMTJ1nenaNkf/TMTj3Cj1KCq2QAE9pvEy8ARdZsY= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251230094225-9a5fe902f561 h1:lfbECpdqczlAvfkpM6q8CjFWLMjRmkf6eHaFgNMOWZ8= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:RlA+oS6G9D5i1RNbUyGdGKTY9fAr9sjQs+zZiEghinU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251230094225-9a5fe902f561 h1:MJLgEelOSdhX/3XU7n9dykExmJ5eoA2rZFXRzbiw+vE= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251230094225-9a5fe902f561 h1:0UIPfoF74+vwCIAYlCq8NGq+em+byaMIJLUIksakdCg= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251230094225-9a5fe902f561/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:h8zDiRnLpHY8oidZqIE0SOiDqswCBSskHWPRqHGD3F0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251230094225-9a5fe902f561 h1:N/svqvPrxxjhPAmZ6OwROQiF3Lg/BoViwGZpnxma3U8= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251230094225-9a5fe902f561 h1:Hoqa5ub53wdkVKBy0rH0pacg5tDdfJq6ZJ4kORdgr+s= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:w6Y6mcEDFXa8kuLIAlM87TuI4UXK9Nl/CGzYDnroTK8= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:4GmOCqjTzAqRZky/epW0zRgEYGbNXNcupxDf2WwWS4Y= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:dWnsBlKtGwd9qebvReFAL64CZ/DCMo5eZiqS7cwuP38= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:ri/eIHXgK+PrK/Z05Gv+xwI7PsrsANWEB2EDr6NkBTY= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:JGN++AgXz57mkZ4UmxEuuvmaj32+yF2mZLNnsfvOm08= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= From 6a750f4522c943fbe96f2c5ce15f5998e96fee86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 30 Dec 2025 22:36:19 +0800 Subject: [PATCH 084/185] Fix missing relay support for Tailscale --- docs/configuration/endpoint/tailscale.md | 15 ++++++ docs/configuration/endpoint/tailscale.zh.md | 15 ++++++ go.mod | 2 +- go.sum | 4 +- option/tailscale.go | 24 +++++----- protocol/tailscale/endpoint.go | 51 +++++++++++++-------- 6 files changed, 78 insertions(+), 33 deletions(-) diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md index 612a86e6..9ac6caf0 100644 --- a/docs/configuration/endpoint/tailscale.md +++ b/docs/configuration/endpoint/tailscale.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "Changes in sing-box 1.13.0" + + :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + !!! question "Since sing-box 1.12.0" ### Structure @@ -20,6 +25,8 @@ icon: material/new-box "exit_node_allow_lan_access": false, "advertise_routes": [], "advertise_exit_node": false, + "relay_server_port": 0, + "relay_server_static_endpoints": [], "udp_timeout": "5m", ... // Dial Fields @@ -89,6 +96,14 @@ Example: `["192.168.1.1/24"]` Indicates whether the node should advertise itself as an exit node. +#### relay_server_port + +The port to listen on for incoming relay connections from other Tailscale nodes. + +#### relay_server_static_endpoints + +Static endpoints to advertise for the relay server. + #### udp_timeout UDP NAT expiration time. diff --git a/docs/configuration/endpoint/tailscale.zh.md b/docs/configuration/endpoint/tailscale.zh.md index 395ecbde..44eb6284 100644 --- a/docs/configuration/endpoint/tailscale.zh.md +++ b/docs/configuration/endpoint/tailscale.zh.md @@ -2,6 +2,11 @@ icon: material/new-box --- +!!! quote "sing-box 1.13.0 中的更改" + + :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + !!! question "自 sing-box 1.12.0 起" ### 结构 @@ -20,6 +25,8 @@ icon: material/new-box "exit_node_allow_lan_access": false, "advertise_routes": [], "advertise_exit_node": false, + "relay_server_port": 0, + "relay_server_static_endpoints": [], "udp_timeout": "5m", ... // 拨号字段 @@ -88,6 +95,14 @@ icon: material/new-box 指示节点是否应将自己通告为出口节点。 +#### relay_server_port + +监听来自其他 Tailscale 节点的中继连接的端口。 + +#### relay_server_static_endpoints + +为中继服务器通告的静态端点。 + #### udp_timeout UDP NAT 过期时间。 diff --git a/go.mod b/go.mod index 4da4a808..06fc3e35 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8 + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.4 github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 3dd6f905..0b649334 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkV github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8 h1:+rb3fIFwFxhCkIt8B/V3bXWZmiNwDWBd22jQMxqY92w= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.3.0.20251225080651-3b25379a5bf8/go.mod h1:HZxL3asFIkcIJtHdnqsdcXsY6d+1iMtq0SPUlX17TGM= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.4 h1:p+9JllOL5Q2pj6bmP9gu+LdjyRg/XxHLTpMfuhuQsY4= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.4/go.mod h1:HZxL3asFIkcIJtHdnqsdcXsY6d+1iMtq0SPUlX17TGM= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= diff --git a/option/tailscale.go b/option/tailscale.go index 1a431887..f8220df3 100644 --- a/option/tailscale.go +++ b/option/tailscale.go @@ -12,17 +12,19 @@ import ( type TailscaleEndpointOptions struct { DialerOptions - StateDirectory string `json:"state_directory,omitempty"` - AuthKey string `json:"auth_key,omitempty"` - ControlURL string `json:"control_url,omitempty"` - Ephemeral bool `json:"ephemeral,omitempty"` - Hostname string `json:"hostname,omitempty"` - AcceptRoutes bool `json:"accept_routes,omitempty"` - ExitNode string `json:"exit_node,omitempty"` - ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"` - AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"` - AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + StateDirectory string `json:"state_directory,omitempty"` + AuthKey string `json:"auth_key,omitempty"` + ControlURL string `json:"control_url,omitempty"` + Ephemeral bool `json:"ephemeral,omitempty"` + Hostname string `json:"hostname,omitempty"` + AcceptRoutes bool `json:"accept_routes,omitempty"` + ExitNode string `json:"exit_node,omitempty"` + ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"` + AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"` + AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` + RelayServerPort *uint16 `json:"relay_server_port,omitempty"` + RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` } type TailscaleDNSServerOptions struct { diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 30cdd718..3bdef142 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -44,6 +44,7 @@ import ( "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" + _ "github.com/sagernet/tailscale/feature/relayserver" "github.com/sagernet/tailscale/ipn" tsDNS "github.com/sagernet/tailscale/net/dns" "github.com/sagernet/tailscale/net/netmon" @@ -91,11 +92,13 @@ type Endpoint struct { routeDomains common.TypedValue[map[string]bool] routePrefixes atomic.Pointer[netipx.IPSet] - acceptRoutes bool - exitNode string - exitNodeAllowLANAccess bool - advertiseRoutes []netip.Prefix - advertiseExitNode bool + acceptRoutes bool + exitNode string + exitNodeAllowLANAccess bool + advertiseRoutes []netip.Prefix + advertiseExitNode bool + relayServerPort *uint16 + relayServerStaticEndpoints []netip.AddrPort udpTimeout time.Duration } @@ -183,20 +186,22 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL }, } return &Endpoint{ - Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil), - ctx: ctx, - router: router, - logger: logger, - dnsRouter: dnsRouter, - network: service.FromContext[adapter.NetworkManager](ctx), - platformInterface: service.FromContext[adapter.PlatformInterface](ctx), - server: server, - acceptRoutes: options.AcceptRoutes, - exitNode: options.ExitNode, - exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, - advertiseRoutes: options.AdvertiseRoutes, - advertiseExitNode: options.AdvertiseExitNode, - udpTimeout: udpTimeout, + Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil), + ctx: ctx, + router: router, + logger: logger, + dnsRouter: dnsRouter, + network: service.FromContext[adapter.NetworkManager](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), + server: server, + acceptRoutes: options.AcceptRoutes, + exitNode: options.ExitNode, + exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, + advertiseRoutes: options.AdvertiseRoutes, + advertiseExitNode: options.AdvertiseExitNode, + relayServerPort: options.RelayServerPort, + relayServerStaticEndpoints: options.RelayServerStaticEndpoints, + udpTimeout: udpTimeout, }, nil } @@ -270,6 +275,14 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { if t.advertiseExitNode { perfs.AdvertiseRoutes = append(perfs.AdvertiseRoutes, tsaddr.ExitRoutes()...) } + if t.relayServerPort != nil { + perfs.RelayServerPort = t.relayServerPort + perfs.RelayServerPortSet = true + } + if len(t.relayServerStaticEndpoints) > 0 { + perfs.RelayServerStaticEndpoints = t.relayServerStaticEndpoints + perfs.RelayServerStaticEndpointsSet = true + } _, err = localBackend.EditPrefs(perfs) if err != nil { return E.Cause(err, "update prefs") From 85f5f6cebbb4834a51552753f6bc03906cacdca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 31 Dec 2025 02:13:49 +0800 Subject: [PATCH 085/185] Disable multipath TCP by default via GODEBUG --- .github/workflows/build.yml | 16 ++++++++-------- .github/workflows/docker.yml | 4 ++-- .github/workflows/linux.yml | 4 ++-- Dockerfile | 2 +- Makefile | 2 +- cmd/internal/build_libbox/main.go | 4 ++-- release/local/common.sh | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72f33fe5..48575a1b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -206,7 +206,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "0" @@ -228,7 +228,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -243,7 +243,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -258,7 +258,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "0" @@ -278,7 +278,7 @@ jobs: export CXX="${CC}++" mkdir -p dist GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -445,7 +445,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -505,7 +505,7 @@ jobs: run: | mkdir -p dist go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` - -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" ` + -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" ` ./cmd/sing-box env: CGO_ENABLED: "0" @@ -517,7 +517,7 @@ jobs: run: | mkdir -p dist go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" ` - -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" ` + -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" ` ./cmd/sing-box env: CGO_ENABLED: "0" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5447457e..e5ec3a20 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -103,7 +103,7 @@ jobs: run: | set -xeuo pipefail go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -115,7 +115,7 @@ jobs: run: | set -xeuo pipefail go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box env: CGO_ENABLED: "0" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5b60f4e7..f74a6c71 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -127,7 +127,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -141,7 +141,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \ + -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ ./cmd/sing-box env: CGO_ENABLED: "0" diff --git a/Dockerfile b/Dockerfile index fb39e8b6..8589b331 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN set -ex \ && go build -v -trimpath -tags \ "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \ -o /go/bin/sing-box \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box FROM --platform=$TARGETPLATFORM alpine AS dist LABEL maintainer="nekohasekai " diff --git a/Makefile b/Makefile index 79b32dd4..121dbaec 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0" +PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)" MAIN = ./cmd/sing-box PREFIX ?= $(shell go env GOPATH) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 6a0ccd08..339cf287 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -60,8 +60,8 @@ func init() { if err != nil { currentTag = "unknown" } - sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0") - debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0") + sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0") + debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0") darwinTags = append(darwinTags, "with_dhcp") diff --git a/release/local/common.sh b/release/local/common.sh index 68a494ba..b1fd367c 100755 --- a/release/local/common.sh +++ b/release/local/common.sh @@ -44,7 +44,7 @@ get_version() { get_ldflags() { local version version=$(get_version) - echo "-X 'github.com/sagernet/sing-box/constant.Version=${version}' -s -w -buildid= -checklinkname=0" + echo "-X 'github.com/sagernet/sing-box/constant.Version=${version}' -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" } build_sing_box() { From 1d4fb83313a68cecadfe76d9e38524dc63500f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 31 Dec 2025 04:02:03 +0800 Subject: [PATCH 086/185] Fix nfqueue fallback --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 06fc3e35..531f1f5e 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8 + github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251230194736-a5db80d71081 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.4 diff --git a/go.sum b/go.sum index 0b649334..eebeeda9 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8 h1:aIgk6YzS/7fNm92CycFWzithdwIc+NAwXGHAJce1dyM= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251226064455-a850c4f8a1c8/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251230194736-a5db80d71081 h1:ZFw+y1RIKasXENuy8jOYfwpyiKBh92HcSqzDFQLd7Yc= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251230194736-a5db80d71081/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From 157e33f2a4656b83f359c37d6286ec676b4d295e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 31 Dec 2025 12:16:15 +0800 Subject: [PATCH 087/185] Add kmod-nft-queue dependency for openwrt package --- .fpm_openwrt | 1 + 1 file changed, 1 insertion(+) diff --git a/.fpm_openwrt b/.fpm_openwrt index ab8b6db6..3223ec8a 100644 --- a/.fpm_openwrt +++ b/.fpm_openwrt @@ -14,6 +14,7 @@ --depends kmod-inet-diag --depends kmod-tun --depends firewall4 +--depends kmod-nft-queue --before-remove release/config/openwrt.prerm From 708ceb3d29144d52e461bdfb5e525ca24a1bfed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 31 Dec 2025 12:33:29 +0800 Subject: [PATCH 088/185] Fix openwrt builds --- .github/workflows/build.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 48575a1b..e5e41584 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,19 +69,19 @@ jobs: strategy: matrix: include: - - { os: linux, arch: amd64, variant: purego, naive: true, openwrt: "x86_64" } + - { os: linux, arch: amd64, variant: purego, naive: true } - { os: linux, arch: amd64, variant: glibc, naive: true } - { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - - { os: linux, arch: arm64, variant: purego, naive: true, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } + - { os: linux, arch: arm64, variant: purego, naive: true } - { os: linux, arch: arm64, variant: glibc, naive: true } - { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - - { os: linux, arch: "386", go386: sse2, openwrt: "i386_pentium4" } + - { os: linux, arch: "386", go386: sse2 } - { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 } - { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" } - - { os: linux, arch: arm, goarm: "7", openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } + - { os: linux, arch: arm, goarm: "7" } - { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" } - { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } @@ -369,12 +369,8 @@ jobs: -p "dist/openwrt.deb" \ --architecture all \ dist/sing-box=/usr/bin/sing-box - SUFFIX="" - if [[ "${{ matrix.variant }}" == "musl" ]]; then - SUFFIX="_musl" - fi for architecture in ${{ matrix.openwrt }}; do - .github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}${SUFFIX}.ipk" + .github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk" done rm "dist/openwrt.deb" - name: Archive From a5db2feb5e91d45f314ad50edb23a8bcb7eea6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 1 Jan 2026 11:53:54 +0800 Subject: [PATCH 089/185] Fix linux musl builds --- .github/CRONET_GO_VERSION | 2 +- go.mod | 48 ++++++++++---------- go.sum | 96 +++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index f987b703..6c59eb79 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -1cc61ad20399081362ccbc18d650432d1a6d42ec +92d4602aba0ab6084673af0fe4887dccbc1049a5 diff --git a/go.mod b/go.mod index 531f1f5e..903afaec 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251230094758-5aef2a9e7f0d - github.com/sagernet/cronet-go/all v0.0.0-20251230094758-5aef2a9e7f0d + github.com/sagernet/cronet-go v0.0.0-20251231120443-1d2d7341cbd8 + github.com/sagernet/cronet-go/all v0.0.0-20251231120443-1d2d7341cbd8 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -102,28 +102,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251230094225-9a5fe902f561 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251230094225-9a5fe902f561 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index eebeeda9..03b38ab3 100644 --- a/go.sum +++ b/go.sum @@ -144,54 +144,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251230094758-5aef2a9e7f0d h1:1oTc5+PjhCjdDro0VYZzwPee0CvTmMbDnyxZAThYvq4= -github.com/sagernet/cronet-go v0.0.0-20251230094758-5aef2a9e7f0d/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251230094758-5aef2a9e7f0d h1:lRwTaavH9XOCf3QPEY9FNkOsy1jLWpo5Lg6Vas9XShs= -github.com/sagernet/cronet-go/all v0.0.0-20251230094758-5aef2a9e7f0d/go.mod h1:Wqam5NDbMkewyy+33wu6wsq4uNHGoskHaon1XsTxQMg= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251230094225-9a5fe902f561 h1:vjswtG+1CNj4kni3haVLzIn8C7RKa3RnFUXG8OofgSE= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:ciuXFp02usTUyj9MpzwlB0bEyEjF0VVINMl5QG5uIu4= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251230094225-9a5fe902f561 h1:qJVWgBiznBwkfQ9EB0TpjgVVeBVHb/COaItekq/EZ5w= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251230094225-9a5fe902f561/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:FEJJEbbmmOv37Lx+JgHlXIdTXzP0TNYHBvzGgbft4fA= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:w9Z3W30bdQrmme2qfJBfkZGUKirPe6N4caCDqp7+ArI= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:7lJXCswd6nIrw31MJYet0sNcIxD9MVxKW/UoSPynBxw= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:dCten+Kq71YCOGxezyIQqpe5zctf4Oxwhk8tgHUuy0g= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:Q6RE/v/XXaCLOh7w7iIuzCRhwWIjk+ikMcKSqO8vA6c= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:bzXuJwzMy/+RLVXYQrNL3cqPYP7pW9OaItF0zyNkBl4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251230094225-9a5fe902f561 h1:bzYwMTJ1nenaNkf/TMTj3Cj1KCq2QAE9pvEy8ARdZsY= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251230094225-9a5fe902f561 h1:lfbECpdqczlAvfkpM6q8CjFWLMjRmkf6eHaFgNMOWZ8= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:RlA+oS6G9D5i1RNbUyGdGKTY9fAr9sjQs+zZiEghinU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251230094225-9a5fe902f561 h1:MJLgEelOSdhX/3XU7n9dykExmJ5eoA2rZFXRzbiw+vE= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251230094225-9a5fe902f561 h1:0UIPfoF74+vwCIAYlCq8NGq+em+byaMIJLUIksakdCg= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251230094225-9a5fe902f561/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:h8zDiRnLpHY8oidZqIE0SOiDqswCBSskHWPRqHGD3F0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251230094225-9a5fe902f561 h1:N/svqvPrxxjhPAmZ6OwROQiF3Lg/BoViwGZpnxma3U8= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251230094225-9a5fe902f561 h1:Hoqa5ub53wdkVKBy0rH0pacg5tDdfJq6ZJ4kORdgr+s= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251230094225-9a5fe902f561/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:w6Y6mcEDFXa8kuLIAlM87TuI4UXK9Nl/CGzYDnroTK8= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:4GmOCqjTzAqRZky/epW0zRgEYGbNXNcupxDf2WwWS4Y= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251230094225-9a5fe902f561 h1:dWnsBlKtGwd9qebvReFAL64CZ/DCMo5eZiqS7cwuP38= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251230094225-9a5fe902f561/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251230094225-9a5fe902f561 h1:ri/eIHXgK+PrK/Z05Gv+xwI7PsrsANWEB2EDr6NkBTY= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251230094225-9a5fe902f561 h1:JGN++AgXz57mkZ4UmxEuuvmaj32+yF2mZLNnsfvOm08= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251230094225-9a5fe902f561/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251231120443-1d2d7341cbd8 h1:yQtAGNBF8c2cYdnsfkbOp8ShdWlpncua96KXqHd9svs= +github.com/sagernet/cronet-go v0.0.0-20251231120443-1d2d7341cbd8/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20251231120443-1d2d7341cbd8 h1:KMmO1p8bO2BTq7NdTNdmjurh7rg6S1vRfHrEvFPw8bY= +github.com/sagernet/cronet-go/all v0.0.0-20251231120443-1d2d7341cbd8/go.mod h1:A4cCrezhvuNnb2K6UVuM+IQDZXBjTi2iK5K/wBu7Olg= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251231115934-b87a9ae9bd80 h1:9dBJpiOdU121TZ+mOb3LW8T5tZ3ZBjSUB9zg2F9Cltg= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:vODDxjRh7CHHnfoapv1+pDFtEMPOFxWnBlVSskXCOSU= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251231115934-b87a9ae9bd80 h1:ivqSFl9JHV63ELQkSTzeUkltLb+owOZ9GoRBTDYC0Jk= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:UKvOG4VuR+8c4VFA11d1dXZfANXNjDecXeZJfmjHTY4= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:qtw9lKXyuDG8kBB3PyhD/xidVhffST7caUEQ2fS6Sbo= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:Vkx2x+f67sOoL5K4YRX6RfIfM8z14YeGqj6Xc/CnzWg= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:+jfTH4yHr+toXrUH2xGUnhql641IIvbBTCKpz9WvTxU= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:Ow8A3f3YAgBJ4HMYJVv8RK0PsvQhAt4GHTBsiuaXJYw= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:HvlLfPq+WxRQtilZD4L3WgMYwZnwGnFoQX29eZtdm1Y= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251231115934-b87a9ae9bd80 h1:nGPM9VpMWhfPDX4SOk7p+GvHYAAv2LOCE4hNIue+eAY= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:GvFk6EH/PmIqxJN2OQarXt6vNrS/tgQ78b/WPvXhAmg= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:SaIqJcgT0D3fK4zdXRA3E9kzq4mtfwkNDzAnv6bFQjE= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:pEnLOmZjgWk9jaezJt/RvMX91Mugq4KwyxdW2mYvrUU= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251231115934-b87a9ae9bd80 h1:/HPZI7eocQxu7Ho5gBoOWEQwH+IgmnD0pG7RVQzeZK8= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:ZxVgKaU1r748EPp2y6GNZhMxtxxbC9xHSmWnJP4qDdM= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:v8cjiAvXjWOkC4Li05MQrQIJ1NH2vK2OWMc/mS/c7FI= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:Hh3at5gCCc2YZNbErv/jMqO4FhYN3oms892bLwK0aHI= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:Mz9JbTXKCfIvnvDxzID7YtkC/5Y/6MG1BWNBmOoTkaQ= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:xM0Z8k3WheuWfJsZNKEBkRyAFK/NEB01UNxElMf8oy8= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:Nvvip5xwIw9GoTg/Tch0uYW11wdc+VhZHTp81LuVZGY= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:BkrIlIUtKHtSXSZe2xGjgEtbP0z1LYo0PXeM25obSSM= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:oJY5DO13q1x4XORhZYR7QVmYdFFhbua5w7Hp74h6Z10= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= From 7d2944eba9f64af0b26f54491b4cc39bb41298e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 2 Jan 2026 19:23:22 +0800 Subject: [PATCH 090/185] Downgrade quic-go to v0.57.1 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 903afaec..8b81eb9c 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 github.com/sagernet/sing v0.8.0-beta.8 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.7 + github.com/sagernet/sing-quic v0.6.0-beta.8 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index 03b38ab3..e6b9081c 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/sagernet/sing v0.8.0-beta.8 h1:hUo0wZ2HGTieV1flEIai96HFhF34mMHVnduRqJ github.com/sagernet/sing v0.8.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.7 h1:Sh6KltQ6nB69S9ZdDKs5oARqkyY99gOaHe1JPxNV7ag= -github.com/sagernet/sing-quic v0.6.0-beta.7/go.mod h1:0NodMFjlAvfLp87Enpx46fQPrGRvmbUsmy5hRLzomtM= +github.com/sagernet/sing-quic v0.6.0-beta.8 h1:Y0P8WTqWpfg80rLFsDfF22QumM+HEAjRQ2o+8Dv+vDs= +github.com/sagernet/sing-quic v0.6.0-beta.8/go.mod h1:Y3YVjPutLHLQvYjGtUFH+w8YmM4MTd19NDzxLZGNGIs= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= From 0caebd3171e0ceb88cd2e91a5b40d1a7b5721e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 4 Jan 2026 02:53:33 +0800 Subject: [PATCH 091/185] platform: Improve interface --- adapter/router.go | 1 + daemon/helper.pb.go | 702 -------------------------- daemon/helper.proto | 61 --- daemon/started_service.go | 8 - daemon/started_service.pb.go | 61 +-- daemon/started_service.proto | 4 - daemon/started_service_grpc.pb.go | 81 --- experimental/libbox/command_client.go | 155 +++++- experimental/libbox/command_server.go | 8 + experimental/libbox/platform.go | 11 +- experimental/libbox/service.go | 24 +- route/router.go | 5 + 12 files changed, 213 insertions(+), 908 deletions(-) delete mode 100644 daemon/helper.pb.go delete mode 100644 daemon/helper.proto diff --git a/adapter/router.go b/adapter/router.go index a0df7124..3d5310c4 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -25,6 +25,7 @@ type Router interface { ConnectionRouterEx RuleSet(tag string) (RuleSet, bool) Rules() []Rule + NeedFindProcess() bool AppendTracker(tracker ConnectionTracker) ResetNetwork() } diff --git a/daemon/helper.pb.go b/daemon/helper.pb.go deleted file mode 100644 index 9a2641c5..00000000 --- a/daemon/helper.pb.go +++ /dev/null @@ -1,702 +0,0 @@ -package daemon - -import ( - reflect "reflect" - sync "sync" - unsafe "unsafe" - - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type SubscribeHelperRequestRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AcceptGetWIFIStateRequests bool `protobuf:"varint,1,opt,name=acceptGetWIFIStateRequests,proto3" json:"acceptGetWIFIStateRequests,omitempty"` - AcceptFindConnectionOwnerRequests bool `protobuf:"varint,2,opt,name=acceptFindConnectionOwnerRequests,proto3" json:"acceptFindConnectionOwnerRequests,omitempty"` - AcceptSendNotificationRequests bool `protobuf:"varint,3,opt,name=acceptSendNotificationRequests,proto3" json:"acceptSendNotificationRequests,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SubscribeHelperRequestRequest) Reset() { - *x = SubscribeHelperRequestRequest{} - mi := &file_daemon_helper_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SubscribeHelperRequestRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubscribeHelperRequestRequest) ProtoMessage() {} - -func (x *SubscribeHelperRequestRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_helper_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubscribeHelperRequestRequest.ProtoReflect.Descriptor instead. -func (*SubscribeHelperRequestRequest) Descriptor() ([]byte, []int) { - return file_daemon_helper_proto_rawDescGZIP(), []int{0} -} - -func (x *SubscribeHelperRequestRequest) GetAcceptGetWIFIStateRequests() bool { - if x != nil { - return x.AcceptGetWIFIStateRequests - } - return false -} - -func (x *SubscribeHelperRequestRequest) GetAcceptFindConnectionOwnerRequests() bool { - if x != nil { - return x.AcceptFindConnectionOwnerRequests - } - return false -} - -func (x *SubscribeHelperRequestRequest) GetAcceptSendNotificationRequests() bool { - if x != nil { - return x.AcceptSendNotificationRequests - } - return false -} - -type HelperRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - // Types that are valid to be assigned to Request: - // - // *HelperRequest_GetWIFIState - // *HelperRequest_FindConnectionOwner - // *HelperRequest_SendNotification - Request isHelperRequest_Request `protobuf_oneof:"request"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *HelperRequest) Reset() { - *x = HelperRequest{} - mi := &file_daemon_helper_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *HelperRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HelperRequest) ProtoMessage() {} - -func (x *HelperRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_helper_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HelperRequest.ProtoReflect.Descriptor instead. -func (*HelperRequest) Descriptor() ([]byte, []int) { - return file_daemon_helper_proto_rawDescGZIP(), []int{1} -} - -func (x *HelperRequest) GetId() int64 { - if x != nil { - return x.Id - } - return 0 -} - -func (x *HelperRequest) GetRequest() isHelperRequest_Request { - if x != nil { - return x.Request - } - return nil -} - -func (x *HelperRequest) GetGetWIFIState() *emptypb.Empty { - if x != nil { - if x, ok := x.Request.(*HelperRequest_GetWIFIState); ok { - return x.GetWIFIState - } - } - return nil -} - -func (x *HelperRequest) GetFindConnectionOwner() *FindConnectionOwnerRequest { - if x != nil { - if x, ok := x.Request.(*HelperRequest_FindConnectionOwner); ok { - return x.FindConnectionOwner - } - } - return nil -} - -func (x *HelperRequest) GetSendNotification() *Notification { - if x != nil { - if x, ok := x.Request.(*HelperRequest_SendNotification); ok { - return x.SendNotification - } - } - return nil -} - -type isHelperRequest_Request interface { - isHelperRequest_Request() -} - -type HelperRequest_GetWIFIState struct { - GetWIFIState *emptypb.Empty `protobuf:"bytes,2,opt,name=getWIFIState,proto3,oneof"` -} - -type HelperRequest_FindConnectionOwner struct { - FindConnectionOwner *FindConnectionOwnerRequest `protobuf:"bytes,3,opt,name=findConnectionOwner,proto3,oneof"` -} - -type HelperRequest_SendNotification struct { - SendNotification *Notification `protobuf:"bytes,4,opt,name=sendNotification,proto3,oneof"` -} - -func (*HelperRequest_GetWIFIState) isHelperRequest_Request() {} - -func (*HelperRequest_FindConnectionOwner) isHelperRequest_Request() {} - -func (*HelperRequest_SendNotification) isHelperRequest_Request() {} - -type FindConnectionOwnerRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - IpProtocol int32 `protobuf:"varint,1,opt,name=ipProtocol,proto3" json:"ipProtocol,omitempty"` - SourceAddress string `protobuf:"bytes,2,opt,name=sourceAddress,proto3" json:"sourceAddress,omitempty"` - SourcePort int32 `protobuf:"varint,3,opt,name=sourcePort,proto3" json:"sourcePort,omitempty"` - DestinationAddress string `protobuf:"bytes,4,opt,name=destinationAddress,proto3" json:"destinationAddress,omitempty"` - DestinationPort int32 `protobuf:"varint,5,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *FindConnectionOwnerRequest) Reset() { - *x = FindConnectionOwnerRequest{} - mi := &file_daemon_helper_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *FindConnectionOwnerRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*FindConnectionOwnerRequest) ProtoMessage() {} - -func (x *FindConnectionOwnerRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_helper_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use FindConnectionOwnerRequest.ProtoReflect.Descriptor instead. -func (*FindConnectionOwnerRequest) Descriptor() ([]byte, []int) { - return file_daemon_helper_proto_rawDescGZIP(), []int{2} -} - -func (x *FindConnectionOwnerRequest) GetIpProtocol() int32 { - if x != nil { - return x.IpProtocol - } - return 0 -} - -func (x *FindConnectionOwnerRequest) GetSourceAddress() string { - if x != nil { - return x.SourceAddress - } - return "" -} - -func (x *FindConnectionOwnerRequest) GetSourcePort() int32 { - if x != nil { - return x.SourcePort - } - return 0 -} - -func (x *FindConnectionOwnerRequest) GetDestinationAddress() string { - if x != nil { - return x.DestinationAddress - } - return "" -} - -func (x *FindConnectionOwnerRequest) GetDestinationPort() int32 { - if x != nil { - return x.DestinationPort - } - return 0 -} - -type Notification struct { - state protoimpl.MessageState `protogen:"open.v1"` - Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` - TypeName string `protobuf:"bytes,2,opt,name=typeName,proto3" json:"typeName,omitempty"` - TypeId int32 `protobuf:"varint,3,opt,name=typeId,proto3" json:"typeId,omitempty"` - Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` - Subtitle string `protobuf:"bytes,5,opt,name=subtitle,proto3" json:"subtitle,omitempty"` - Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` - OpenURL string `protobuf:"bytes,7,opt,name=openURL,proto3" json:"openURL,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Notification) Reset() { - *x = Notification{} - mi := &file_daemon_helper_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Notification) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Notification) ProtoMessage() {} - -func (x *Notification) ProtoReflect() protoreflect.Message { - mi := &file_daemon_helper_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Notification.ProtoReflect.Descriptor instead. -func (*Notification) Descriptor() ([]byte, []int) { - return file_daemon_helper_proto_rawDescGZIP(), []int{3} -} - -func (x *Notification) GetIdentifier() string { - if x != nil { - return x.Identifier - } - return "" -} - -func (x *Notification) GetTypeName() string { - if x != nil { - return x.TypeName - } - return "" -} - -func (x *Notification) GetTypeId() int32 { - if x != nil { - return x.TypeId - } - return 0 -} - -func (x *Notification) GetTitle() string { - if x != nil { - return x.Title - } - return "" -} - -func (x *Notification) GetSubtitle() string { - if x != nil { - return x.Subtitle - } - return "" -} - -func (x *Notification) GetBody() string { - if x != nil { - return x.Body - } - return "" -} - -func (x *Notification) GetOpenURL() string { - if x != nil { - return x.OpenURL - } - return "" -} - -type HelperResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - // Types that are valid to be assigned to Response: - // - // *HelperResponse_WifiState - // *HelperResponse_Error - // *HelperResponse_ConnectionOwner - Response isHelperResponse_Response `protobuf_oneof:"response"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *HelperResponse) Reset() { - *x = HelperResponse{} - mi := &file_daemon_helper_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *HelperResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HelperResponse) ProtoMessage() {} - -func (x *HelperResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_helper_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HelperResponse.ProtoReflect.Descriptor instead. -func (*HelperResponse) Descriptor() ([]byte, []int) { - return file_daemon_helper_proto_rawDescGZIP(), []int{4} -} - -func (x *HelperResponse) GetId() int64 { - if x != nil { - return x.Id - } - return 0 -} - -func (x *HelperResponse) GetResponse() isHelperResponse_Response { - if x != nil { - return x.Response - } - return nil -} - -func (x *HelperResponse) GetWifiState() *WIFIState { - if x != nil { - if x, ok := x.Response.(*HelperResponse_WifiState); ok { - return x.WifiState - } - } - return nil -} - -func (x *HelperResponse) GetError() string { - if x != nil { - if x, ok := x.Response.(*HelperResponse_Error); ok { - return x.Error - } - } - return "" -} - -func (x *HelperResponse) GetConnectionOwner() *ConnectionOwner { - if x != nil { - if x, ok := x.Response.(*HelperResponse_ConnectionOwner); ok { - return x.ConnectionOwner - } - } - return nil -} - -type isHelperResponse_Response interface { - isHelperResponse_Response() -} - -type HelperResponse_WifiState struct { - WifiState *WIFIState `protobuf:"bytes,2,opt,name=wifiState,proto3,oneof"` -} - -type HelperResponse_Error struct { - Error string `protobuf:"bytes,3,opt,name=error,proto3,oneof"` -} - -type HelperResponse_ConnectionOwner struct { - ConnectionOwner *ConnectionOwner `protobuf:"bytes,4,opt,name=connectionOwner,proto3,oneof"` -} - -func (*HelperResponse_WifiState) isHelperResponse_Response() {} - -func (*HelperResponse_Error) isHelperResponse_Response() {} - -func (*HelperResponse_ConnectionOwner) isHelperResponse_Response() {} - -type ConnectionOwner struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` - UserName string `protobuf:"bytes,2,opt,name=userName,proto3" json:"userName,omitempty"` - ProcessPath string `protobuf:"bytes,3,opt,name=processPath,proto3" json:"processPath,omitempty"` - AndroidPackageName string `protobuf:"bytes,4,opt,name=androidPackageName,proto3" json:"androidPackageName,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ConnectionOwner) Reset() { - *x = ConnectionOwner{} - mi := &file_daemon_helper_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ConnectionOwner) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConnectionOwner) ProtoMessage() {} - -func (x *ConnectionOwner) ProtoReflect() protoreflect.Message { - mi := &file_daemon_helper_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConnectionOwner.ProtoReflect.Descriptor instead. -func (*ConnectionOwner) Descriptor() ([]byte, []int) { - return file_daemon_helper_proto_rawDescGZIP(), []int{5} -} - -func (x *ConnectionOwner) GetUserId() int32 { - if x != nil { - return x.UserId - } - return 0 -} - -func (x *ConnectionOwner) GetUserName() string { - if x != nil { - return x.UserName - } - return "" -} - -func (x *ConnectionOwner) GetProcessPath() string { - if x != nil { - return x.ProcessPath - } - return "" -} - -func (x *ConnectionOwner) GetAndroidPackageName() string { - if x != nil { - return x.AndroidPackageName - } - return "" -} - -type WIFIState struct { - state protoimpl.MessageState `protogen:"open.v1"` - Ssid string `protobuf:"bytes,1,opt,name=ssid,proto3" json:"ssid,omitempty"` - Bssid string `protobuf:"bytes,2,opt,name=bssid,proto3" json:"bssid,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *WIFIState) Reset() { - *x = WIFIState{} - mi := &file_daemon_helper_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *WIFIState) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WIFIState) ProtoMessage() {} - -func (x *WIFIState) ProtoReflect() protoreflect.Message { - mi := &file_daemon_helper_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WIFIState.ProtoReflect.Descriptor instead. -func (*WIFIState) Descriptor() ([]byte, []int) { - return file_daemon_helper_proto_rawDescGZIP(), []int{6} -} - -func (x *WIFIState) GetSsid() string { - if x != nil { - return x.Ssid - } - return "" -} - -func (x *WIFIState) GetBssid() string { - if x != nil { - return x.Bssid - } - return "" -} - -var File_daemon_helper_proto protoreflect.FileDescriptor - -const file_daemon_helper_proto_rawDesc = "" + - "\n" + - "\x13daemon/helper.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xf5\x01\n" + - "\x1dSubscribeHelperRequestRequest\x12>\n" + - "\x1aacceptGetWIFIStateRequests\x18\x01 \x01(\bR\x1aacceptGetWIFIStateRequests\x12L\n" + - "!acceptFindConnectionOwnerRequests\x18\x02 \x01(\bR!acceptFindConnectionOwnerRequests\x12F\n" + - "\x1eacceptSendNotificationRequests\x18\x03 \x01(\bR\x1eacceptSendNotificationRequests\"\x84\x02\n" + - "\rHelperRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id\x12<\n" + - "\fgetWIFIState\x18\x02 \x01(\v2\x16.google.protobuf.EmptyH\x00R\fgetWIFIState\x12V\n" + - "\x13findConnectionOwner\x18\x03 \x01(\v2\".daemon.FindConnectionOwnerRequestH\x00R\x13findConnectionOwner\x12B\n" + - "\x10sendNotification\x18\x04 \x01(\v2\x14.daemon.NotificationH\x00R\x10sendNotificationB\t\n" + - "\arequest\"\xdc\x01\n" + - "\x1aFindConnectionOwnerRequest\x12\x1e\n" + - "\n" + - "ipProtocol\x18\x01 \x01(\x05R\n" + - "ipProtocol\x12$\n" + - "\rsourceAddress\x18\x02 \x01(\tR\rsourceAddress\x12\x1e\n" + - "\n" + - "sourcePort\x18\x03 \x01(\x05R\n" + - "sourcePort\x12.\n" + - "\x12destinationAddress\x18\x04 \x01(\tR\x12destinationAddress\x12(\n" + - "\x0fdestinationPort\x18\x05 \x01(\x05R\x0fdestinationPort\"\xc2\x01\n" + - "\fNotification\x12\x1e\n" + - "\n" + - "identifier\x18\x01 \x01(\tR\n" + - "identifier\x12\x1a\n" + - "\btypeName\x18\x02 \x01(\tR\btypeName\x12\x16\n" + - "\x06typeId\x18\x03 \x01(\x05R\x06typeId\x12\x14\n" + - "\x05title\x18\x04 \x01(\tR\x05title\x12\x1a\n" + - "\bsubtitle\x18\x05 \x01(\tR\bsubtitle\x12\x12\n" + - "\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" + - "\aopenURL\x18\a \x01(\tR\aopenURL\"\xbc\x01\n" + - "\x0eHelperResponse\x12\x0e\n" + - "\x02id\x18\x01 \x01(\x03R\x02id\x121\n" + - "\twifiState\x18\x02 \x01(\v2\x11.daemon.WIFIStateH\x00R\twifiState\x12\x16\n" + - "\x05error\x18\x03 \x01(\tH\x00R\x05error\x12C\n" + - "\x0fconnectionOwner\x18\x04 \x01(\v2\x17.daemon.ConnectionOwnerH\x00R\x0fconnectionOwnerB\n" + - "\n" + - "\bresponse\"\x97\x01\n" + - "\x0fConnectionOwner\x12\x16\n" + - "\x06userId\x18\x01 \x01(\x05R\x06userId\x12\x1a\n" + - "\buserName\x18\x02 \x01(\tR\buserName\x12 \n" + - "\vprocessPath\x18\x03 \x01(\tR\vprocessPath\x12.\n" + - "\x12androidPackageName\x18\x04 \x01(\tR\x12androidPackageName\"5\n" + - "\tWIFIState\x12\x12\n" + - "\x04ssid\x18\x01 \x01(\tR\x04ssid\x12\x14\n" + - "\x05bssid\x18\x02 \x01(\tR\x05bssidB%Z#github.com/sagernet/sing-box/daemonb\x06proto3" - -var ( - file_daemon_helper_proto_rawDescOnce sync.Once - file_daemon_helper_proto_rawDescData []byte -) - -func file_daemon_helper_proto_rawDescGZIP() []byte { - file_daemon_helper_proto_rawDescOnce.Do(func() { - file_daemon_helper_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc))) - }) - return file_daemon_helper_proto_rawDescData -} - -var ( - file_daemon_helper_proto_msgTypes = make([]protoimpl.MessageInfo, 7) - file_daemon_helper_proto_goTypes = []any{ - (*SubscribeHelperRequestRequest)(nil), // 0: daemon.SubscribeHelperRequestRequest - (*HelperRequest)(nil), // 1: daemon.HelperRequest - (*FindConnectionOwnerRequest)(nil), // 2: daemon.FindConnectionOwnerRequest - (*Notification)(nil), // 3: daemon.Notification - (*HelperResponse)(nil), // 4: daemon.HelperResponse - (*ConnectionOwner)(nil), // 5: daemon.ConnectionOwner - (*WIFIState)(nil), // 6: daemon.WIFIState - (*emptypb.Empty)(nil), // 7: google.protobuf.Empty - } -) - -var file_daemon_helper_proto_depIdxs = []int32{ - 7, // 0: daemon.HelperRequest.getWIFIState:type_name -> google.protobuf.Empty - 2, // 1: daemon.HelperRequest.findConnectionOwner:type_name -> daemon.FindConnectionOwnerRequest - 3, // 2: daemon.HelperRequest.sendNotification:type_name -> daemon.Notification - 6, // 3: daemon.HelperResponse.wifiState:type_name -> daemon.WIFIState - 5, // 4: daemon.HelperResponse.connectionOwner:type_name -> daemon.ConnectionOwner - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name -} - -func init() { file_daemon_helper_proto_init() } -func file_daemon_helper_proto_init() { - if File_daemon_helper_proto != nil { - return - } - file_daemon_helper_proto_msgTypes[1].OneofWrappers = []any{ - (*HelperRequest_GetWIFIState)(nil), - (*HelperRequest_FindConnectionOwner)(nil), - (*HelperRequest_SendNotification)(nil), - } - file_daemon_helper_proto_msgTypes[4].OneofWrappers = []any{ - (*HelperResponse_WifiState)(nil), - (*HelperResponse_Error)(nil), - (*HelperResponse_ConnectionOwner)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)), - NumEnums: 0, - NumMessages: 7, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_daemon_helper_proto_goTypes, - DependencyIndexes: file_daemon_helper_proto_depIdxs, - MessageInfos: file_daemon_helper_proto_msgTypes, - }.Build() - File_daemon_helper_proto = out.File - file_daemon_helper_proto_goTypes = nil - file_daemon_helper_proto_depIdxs = nil -} diff --git a/daemon/helper.proto b/daemon/helper.proto deleted file mode 100644 index 8cf28075..00000000 --- a/daemon/helper.proto +++ /dev/null @@ -1,61 +0,0 @@ -syntax = "proto3"; - -package daemon; -option go_package = "github.com/sagernet/sing-box/daemon"; - -import "google/protobuf/empty.proto"; - -message SubscribeHelperRequestRequest { - bool acceptGetWIFIStateRequests = 1; - bool acceptFindConnectionOwnerRequests = 2; - bool acceptSendNotificationRequests = 3; -} - -message HelperRequest { - int64 id = 1; - oneof request { - google.protobuf.Empty getWIFIState = 2; - FindConnectionOwnerRequest findConnectionOwner = 3; - Notification sendNotification = 4; - } -} - -message FindConnectionOwnerRequest { - int32 ipProtocol = 1; - string sourceAddress = 2; - int32 sourcePort = 3; - string destinationAddress = 4; - int32 destinationPort = 5; -} - -message Notification { - string identifier = 1; - string typeName = 2; - int32 typeId = 3; - string title = 4; - string subtitle = 5; - string body = 6; - string openURL = 7; -} - -message HelperResponse { - int64 id = 1; - oneof response { - WIFIState wifiState = 2; - string error = 3; - ConnectionOwner connectionOwner = 4; - } -} - -message ConnectionOwner { - int32 userId = 1; - string userName = 2; - string processPath = 3; - string androidPackageName = 4; -} - -message WIFIState { - string ssid = 1; - string bssid = 2; -} - diff --git a/daemon/started_service.go b/daemon/started_service.go index 2eb46106..7176f058 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -823,14 +823,6 @@ func (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty) return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil } -func (s *StartedService) SubscribeHelperEvents(empty *emptypb.Empty, server grpc.ServerStreamingServer[HelperRequest]) error { - return os.ErrInvalid -} - -func (s *StartedService) SendHelperResponse(ctx context.Context, response *HelperResponse) (*emptypb.Empty, error) { - return nil, os.ErrInvalid -} - func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() { } diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go index 5f3726fe..b00a1fb2 100644 --- a/daemon/started_service.pb.go +++ b/daemon/started_service.pb.go @@ -1751,7 +1751,7 @@ var File_daemon_started_service_proto protoreflect.FileDescriptor const file_daemon_started_service_proto_rawDesc = "" + "\n" + - "\x1cdaemon/started_service.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\x1a\x13daemon/helper.proto\"\xad\x01\n" + + "\x1cdaemon/started_service.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xad\x01\n" + "\rServiceStatus\x122\n" + "\x06status\x18\x01 \x01(\x0e2\x1a.daemon.ServiceStatus.TypeR\x06status\x12\"\n" + "\ferrorMessage\x18\x02 \x01(\tR\ferrorMessage\"D\n" + @@ -1883,7 +1883,7 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x10ConnectionSortBy\x12\b\n" + "\x04DATE\x10\x00\x12\v\n" + "\aTRAFFIC\x10\x01\x12\x11\n" + - "\rTOTAL_TRAFFIC\x10\x022\xf4\f\n" + + "\rTOTAL_TRAFFIC\x10\x022\xe0\v\n" + "\x0eStartedService\x12=\n" + "\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" + "\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" + @@ -1905,9 +1905,7 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" + "\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" + "\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12;\n" + - "\fGetStartedAt\x12\x16.google.protobuf.Empty\x1a\x11.daemon.StartedAt\"\x00\x12J\n" + - "\x15SubscribeHelperEvents\x12\x16.google.protobuf.Empty\x1a\x15.daemon.HelperRequest\"\x000\x01\x12F\n" + - "\x12SendHelperResponse\x12\x16.daemon.HelperResponse\x1a\x16.google.protobuf.Empty\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" + "\fGetStartedAt\x12\x16.google.protobuf.Empty\x1a\x11.daemon.StartedAt\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3" var ( file_daemon_started_service_proto_rawDescOnce sync.Once @@ -1955,8 +1953,6 @@ var ( (*StartedAt)(nil), // 27: daemon.StartedAt (*Log_Message)(nil), // 28: daemon.Log.Message (*emptypb.Empty)(nil), // 29: google.protobuf.Empty - (*HelperResponse)(nil), // 30: daemon.HelperResponse - (*HelperRequest)(nil), // 31: daemon.HelperRequest } ) @@ -1993,33 +1989,29 @@ var file_daemon_started_service_proto_depIdxs = []int32{ 29, // 29: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty 29, // 30: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty 29, // 31: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty - 29, // 32: daemon.StartedService.SubscribeHelperEvents:input_type -> google.protobuf.Empty - 30, // 33: daemon.StartedService.SendHelperResponse:input_type -> daemon.HelperResponse - 29, // 34: daemon.StartedService.StopService:output_type -> google.protobuf.Empty - 29, // 35: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty - 4, // 36: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus - 7, // 37: daemon.StartedService.SubscribeLog:output_type -> daemon.Log - 8, // 38: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel - 29, // 39: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty - 9, // 40: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status - 10, // 41: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups - 17, // 42: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus - 16, // 43: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode - 29, // 44: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty - 29, // 45: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty - 29, // 46: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty - 29, // 47: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty - 18, // 48: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus - 29, // 49: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty - 21, // 50: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections - 29, // 51: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty - 29, // 52: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty - 25, // 53: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings - 27, // 54: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt - 31, // 55: daemon.StartedService.SubscribeHelperEvents:output_type -> daemon.HelperRequest - 29, // 56: daemon.StartedService.SendHelperResponse:output_type -> google.protobuf.Empty - 34, // [34:57] is the sub-list for method output_type - 11, // [11:34] is the sub-list for method input_type + 29, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty + 29, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty + 4, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 7, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 8, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 29, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty + 9, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 10, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 17, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 16, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 29, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty + 29, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty + 29, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty + 29, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty + 18, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 29, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty + 21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections + 29, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty + 29, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty + 25, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings + 27, // 52: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt + 32, // [32:53] is the sub-list for method output_type + 11, // [11:32] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name 11, // [11:11] is the sub-list for extension extendee 0, // [0:11] is the sub-list for field type_name @@ -2030,7 +2022,6 @@ func file_daemon_started_service_proto_init() { if File_daemon_started_service_proto != nil { return } - file_daemon_helper_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/daemon/started_service.proto b/daemon/started_service.proto index a063d7a3..cd501cb5 100644 --- a/daemon/started_service.proto +++ b/daemon/started_service.proto @@ -4,7 +4,6 @@ package daemon; option go_package = "github.com/sagernet/sing-box/daemon"; import "google/protobuf/empty.proto"; -import "daemon/helper.proto"; service StartedService { rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty); @@ -33,9 +32,6 @@ service StartedService { rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {} rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {} rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {} - - rpc SubscribeHelperEvents(google.protobuf.Empty) returns(stream HelperRequest) {} - rpc SendHelperResponse(HelperResponse) returns(google.protobuf.Empty) {} } message ServiceStatus { diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go index a9580469..1fd09e40 100644 --- a/daemon/started_service_grpc.pb.go +++ b/daemon/started_service_grpc.pb.go @@ -36,8 +36,6 @@ const ( StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections" StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings" StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt" - StartedService_SubscribeHelperEvents_FullMethodName = "/daemon.StartedService/SubscribeHelperEvents" - StartedService_SendHelperResponse_FullMethodName = "/daemon.StartedService/SendHelperResponse" ) // StartedServiceClient is the client API for StartedService service. @@ -65,8 +63,6 @@ type StartedServiceClient interface { CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error) - SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) - SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) } type startedServiceClient struct { @@ -341,35 +337,6 @@ func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Emp return out, nil } -func (c *startedServiceClient) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeHelperEvents_FullMethodName, cOpts...) - if err != nil { - return nil, err - } - x := &grpc.GenericClientStream[emptypb.Empty, HelperRequest]{ClientStream: stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type StartedService_SubscribeHelperEventsClient = grpc.ServerStreamingClient[HelperRequest] - -func (c *startedServiceClient) SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, StartedService_SendHelperResponse_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - // StartedServiceServer is the server API for StartedService service. // All implementations must embed UnimplementedStartedServiceServer // for forward compatibility. @@ -395,8 +362,6 @@ type StartedServiceServer interface { CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) - SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error - SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) mustEmbedUnimplementedStartedServiceServer() } @@ -490,14 +455,6 @@ func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) { return nil, status.Errorf(codes.Unimplemented, "method GetStartedAt not implemented") } - -func (UnimplementedStartedServiceServer) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error { - return status.Errorf(codes.Unimplemented, "method SubscribeHelperEvents not implemented") -} - -func (UnimplementedStartedServiceServer) SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SendHelperResponse not implemented") -} func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {} func (UnimplementedStartedServiceServer) testEmbeddedByValue() {} @@ -855,35 +812,6 @@ func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _StartedService_SubscribeHelperEvents_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(emptypb.Empty) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(StartedServiceServer).SubscribeHelperEvents(m, &grpc.GenericServerStream[emptypb.Empty, HelperRequest]{ServerStream: stream}) -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type StartedService_SubscribeHelperEventsServer = grpc.ServerStreamingServer[HelperRequest] - -func _StartedService_SendHelperResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(HelperResponse) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(StartedServiceServer).SendHelperResponse(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: StartedService_SendHelperResponse_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(StartedServiceServer).SendHelperResponse(ctx, req.(*HelperResponse)) - } - return interceptor(ctx, in, info, handler) -} - // StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -951,10 +879,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetStartedAt", Handler: _StartedService_GetStartedAt_Handler, }, - { - MethodName: "SendHelperResponse", - Handler: _StartedService_SendHelperResponse_Handler, - }, }, Streams: []grpc.StreamDesc{ { @@ -987,11 +911,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{ Handler: _StartedService_SubscribeConnections_Handler, ServerStreams: true, }, - { - StreamName: "SubscribeHelperEvents", - Handler: _StartedService_SubscribeHelperEvents_Handler, - ServerStreams: true, - }, }, Metadata: "daemon/started_service.proto", } diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 8b60332e..6f8b5acc 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -62,6 +62,16 @@ type LogIterator interface { Next() *LogEntry } +type XPCDialer interface { + DialXPC() (int32, error) +} + +var sXPCDialer XPCDialer + +func SetXPCDialer(dialer XPCDialer) { + sXPCDialer = dialer +} + func NewStandaloneCommandClient() *CommandClient { return new(CommandClient) } @@ -117,13 +127,113 @@ func (c *CommandClient) Connect() error { c.clientMutex.Lock() common.Close(common.PtrOrNil(c.grpcConn)) - conn, err := c.grpcDial() + if sXPCDialer != nil { + fd, err := sXPCDialer.DialXPC() + if err != nil { + c.clientMutex.Unlock() + return err + } + file := os.NewFile(uintptr(fd), "xpc-command-socket") + if file == nil { + c.clientMutex.Unlock() + return E.New("invalid file descriptor") + } + netConn, err := net.FileConn(file) + if err != nil { + file.Close() + c.clientMutex.Unlock() + return E.Cause(err, "create connection from fd") + } + file.Close() + + clientOptions := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return netConn, nil + }), + grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), + grpc.WithStreamInterceptor(streamClientAuthInterceptor), + } + + grpcConn, err := grpc.NewClient("passthrough:///xpc", clientOptions...) + if err != nil { + netConn.Close() + c.clientMutex.Unlock() + return err + } + + c.grpcConn = grpcConn + c.grpcClient = daemon.NewStartedServiceClient(grpcConn) + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.clientMutex.Unlock() + } else { + conn, err := c.grpcDial() + if err != nil { + c.clientMutex.Unlock() + return err + } + c.grpcConn = conn + c.grpcClient = daemon.NewStartedServiceClient(conn) + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.clientMutex.Unlock() + } + + c.handler.Connected() + for _, command := range c.options.commands { + switch command { + case CommandLog: + go c.handleLogStream() + case CommandStatus: + go c.handleStatusStream() + case CommandGroup: + go c.handleGroupStream() + case CommandClashMode: + go c.handleClashModeStream() + case CommandConnections: + go c.handleConnectionsStream() + default: + return E.New("unknown command: ", command) + } + } + return nil +} + +func (c *CommandClient) ConnectWithFD(fd int32) error { + c.clientMutex.Lock() + common.Close(common.PtrOrNil(c.grpcConn)) + + file := os.NewFile(uintptr(fd), "xpc-command-socket") + if file == nil { + c.clientMutex.Unlock() + return E.New("invalid file descriptor") + } + + netConn, err := net.FileConn(file) if err != nil { + file.Close() + c.clientMutex.Unlock() + return E.Cause(err, "create connection from fd") + } + file.Close() + + clientOptions := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return netConn, nil + }), + grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), + grpc.WithStreamInterceptor(streamClientAuthInterceptor), + } + + grpcConn, err := grpc.NewClient("passthrough:///xpc", clientOptions...) + if err != nil { + netConn.Close() c.clientMutex.Unlock() return err } - c.grpcConn = conn - c.grpcClient = daemon.NewStartedServiceClient(conn) + + c.grpcConn = grpcConn + c.grpcClient = daemon.NewStartedServiceClient(grpcConn) c.ctx, c.cancel = context.WithCancel(context.Background()) c.clientMutex.Unlock() @@ -171,6 +281,45 @@ func (c *CommandClient) getClientForCall() (daemon.StartedServiceClient, error) return c.grpcClient, nil } + if sXPCDialer != nil { + fd, err := sXPCDialer.DialXPC() + if err != nil { + return nil, err + } + file := os.NewFile(uintptr(fd), "xpc-command-socket") + if file == nil { + return nil, E.New("invalid file descriptor") + } + netConn, err := net.FileConn(file) + if err != nil { + file.Close() + return nil, E.Cause(err, "create connection from fd") + } + file.Close() + + clientOptions := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return netConn, nil + }), + grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), + grpc.WithStreamInterceptor(streamClientAuthInterceptor), + } + + grpcConn, err := grpc.NewClient("passthrough:///xpc", clientOptions...) + if err != nil { + netConn.Close() + return nil, err + } + + c.grpcConn = grpcConn + c.grpcClient = daemon.NewStartedServiceClient(grpcConn) + if c.ctx == nil { + c.ctx, c.cancel = context.WithCancel(context.Background()) + } + return c.grpcClient, nil + } + conn, err := c.grpcDial() if err != nil { return nil, err diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index b25f3065..f889c381 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -195,6 +195,14 @@ func (s *CommandServer) NeedWIFIState() bool { return instance.Box().Network().NeedWIFIState() } +func (s *CommandServer) NeedFindProcess() bool { + instance := s.StartedService.Instance() + if instance == nil || instance.Box() == nil { + return false + } + return instance.Box().Router().NeedFindProcess() +} + func (s *CommandServer) Pause() { instance := s.StartedService.Instance() if instance == nil || instance.PauseManager() == nil { diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 22345201..63c54ccf 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -11,9 +11,7 @@ type PlatformInterface interface { AutoDetectInterfaceControl(fd int32) error OpenTun(options TunOptions) (int32, error) UseProcFS() bool - FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) - PackageNameByUid(uid int32) (string, error) - UIDByPackageName(packageName string) (int32, error) + FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (*ConnectionOwner, error) StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error GetInterfaces() (NetworkInterfaceIterator, error) @@ -25,6 +23,13 @@ type PlatformInterface interface { SendNotification(notification *Notification) error } +type ConnectionOwner struct { + UserId int32 + UserName string + ProcessPath string + AndroidPackageName string +} + type InterfaceUpdateListener interface { UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool) } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 2cc270f4..2d5d9be4 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -166,7 +166,6 @@ func (w *platformInterfaceWrapper) UsePlatformConnectionOwnerFinder() bool { } func (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) { - var uid int32 if w.useProcFS { var source netip.AddrPort var destination netip.AddrPort @@ -185,21 +184,24 @@ func (w *platformInterfaceWrapper) FindConnectionOwner(request *adapter.FindConn return nil, E.New("unknown protocol: ", request.IpProtocol) } - uid = procfs.ResolveSocketByProcSearch(network, source, destination) + uid := procfs.ResolveSocketByProcSearch(network, source, destination) if uid == -1 { return nil, E.New("procfs: not found") } - } else { - var err error - uid, err = w.iif.FindConnectionOwner(request.IpProtocol, request.SourceAddress, request.SourcePort, request.DestinationAddress, request.DestinationPort) - if err != nil { - return nil, err - } + return &adapter.ConnectionOwner{ + UserId: uid, + }, nil + } + + result, err := w.iif.FindConnectionOwner(request.IpProtocol, request.SourceAddress, request.SourcePort, request.DestinationAddress, request.DestinationPort) + if err != nil { + return nil, err } - packageName, _ := w.iif.PackageNameByUid(uid) return &adapter.ConnectionOwner{ - UserId: uid, - AndroidPackageName: packageName, + UserId: result.UserId, + UserName: result.UserName, + ProcessPath: result.ProcessPath, + AndroidPackageName: result.AndroidPackageName, }, nil } diff --git a/route/router.go b/route/router.go index fe72c1c5..5c73cb1c 100644 --- a/route/router.go +++ b/route/router.go @@ -121,6 +121,7 @@ func (r *Router) Start(stage adapter.StartStage) error { if C.IsAndroid && r.platformInterface != nil { needFindProcess = true } + r.needFindProcess = needFindProcess if needFindProcess { if r.platformInterface != nil && r.platformInterface.UsePlatformConnectionOwnerFinder() { r.processSearcher = newPlatformSearcher(r.platformInterface) @@ -201,6 +202,10 @@ func (r *Router) AppendTracker(tracker adapter.ConnectionTracker) { r.trackers = append(r.trackers, tracker) } +func (r *Router) NeedFindProcess() bool { + return r.needFindProcess +} + func (r *Router) ResetNetwork() { r.network.ResetNetwork() r.dns.ResetNetwork() From 0e0e838ff511b92acf2b9adbd748d7a7fd657c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 Jan 2026 17:46:28 +0800 Subject: [PATCH 092/185] platform: Update apple build comamnds --- Makefile | 70 ++++++++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 121dbaec..ef0eefe4 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ build_ios: cd ../sing-box-for-apple && \ rm -rf build/SFI.xcarchive && \ xcodebuild clean -scheme SFI && \ - xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates + xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌" upload_ios_app_store: cd ../sing-box-for-apple && \ @@ -130,7 +130,7 @@ release_ios: build_ios upload_ios_app_store build_macos: cd ../sing-box-for-apple && \ rm -rf build/SFM.xcarchive && \ - xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates + xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌" upload_macos_app_store: cd ../sing-box-for-apple && \ @@ -139,54 +139,50 @@ upload_macos_app_store: release_macos: build_macos upload_macos_app_store build_macos_standalone: - cd ../sing-box-for-apple && \ - rm -rf build/SFM.System.xcarchive && \ - xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates + $(MAKE) -C ../sing-box-for-apple archive_macos_standalone build_macos_dmg: - rm -rf dist/SFM - mkdir -p dist/SFM - cd ../sing-box-for-apple && \ - rm -rf build/SFM.System && \ - rm -rf build/SFM.dmg && \ - xcodebuild -exportArchive \ - -archivePath "build/SFM.System.xcarchive" \ - -exportOptionsPlist SFM.System/Export.plist -allowProvisioningUpdates \ - -exportPath "build/SFM.System" && \ - create-dmg \ - --volname "sing-box" \ - --volicon "build/SFM.System/SFM.app/Contents/Resources/AppIcon.icns" \ - --icon "SFM.app" 0 0 \ - --hide-extension "SFM.app" \ - --app-drop-link 0 0 \ - --skip-jenkins \ - "../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app" + $(MAKE) -C ../sing-box-for-apple build_macos_dmg + +build_macos_pkg: + $(MAKE) -C ../sing-box-for-apple build_macos_pkg notarize_macos_dmg: - xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \ - --keychain-profile "notarytool-password" \ - --no-s3-acceleration + $(MAKE) -C ../sing-box-for-apple notarize_macos_dmg + +notarize_macos_pkg: + $(MAKE) -C ../sing-box-for-apple notarize_macos_pkg upload_macos_dmg: - cd dist/SFM && \ - cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \ - ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg" + mkdir -p dist/SFM + cp ../sing-box-for-apple/build/SFM-Apple.dmg "dist/SFM/SFM-${VERSION}-Apple.dmg" + cp ../sing-box-for-apple/build/SFM-Intel.dmg "dist/SFM/SFM-${VERSION}-Intel.dmg" + cp ../sing-box-for-apple/build/SFM-Universal.dmg "dist/SFM/SFM-${VERSION}-Universal.dmg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.dmg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.dmg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.dmg" + +upload_macos_pkg: + mkdir -p dist/SFM + cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg" + cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg" + cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg" upload_macos_dsyms: - pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \ - zip -r SFM.dSYMs.zip dSYMs && \ - mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \ - popd && \ - cd dist/SFM && \ - cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \ - ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip" + mkdir -p dist/SFM + cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs + cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip" + ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip" -release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms +release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms build_tvos: cd ../sing-box-for-apple && \ rm -rf build/SFT.xcarchive && \ - xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates + xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌" upload_tvos_app_store: cd ../sing-box-for-apple && \ From bd9935eebb2ea3e415891d5c8b70f0df691ecde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 6 Jan 2026 20:57:02 +0800 Subject: [PATCH 093/185] platform: Fix gomobile build --- Makefile | 4 ++-- cmd/internal/build_libbox/main.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index ef0eefe4..d5be4af9 100644 --- a/Makefile +++ b/Makefile @@ -248,8 +248,8 @@ lib: go run ./cmd/internal/build_libbox -target ios lib_install: - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.10 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.10 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.11 docs: venv/bin/mkdocs serve diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 339cf287..62f364c4 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -194,7 +194,7 @@ func buildApple() { } else if debugEnabled { bindTarget = "ios" } else { - bindTarget = "ios,tvos,macos" + bindTarget = "ios,iossimulator,tvos,tvossimulator,macos" } args := []string{ diff --git a/go.mod b/go.mod index 8b81eb9c..90fdb401 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/cronet-go v0.0.0-20251231120443-1d2d7341cbd8 github.com/sagernet/cronet-go/all v0.0.0-20251231120443-1d2d7341cbd8 github.com/sagernet/fswatch v0.1.1 - github.com/sagernet/gomobile v0.1.10 + github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 github.com/sagernet/sing v0.8.0-beta.8 diff --git a/go.sum b/go.sum index e6b9081c..26e745fd 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= -github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= -github.com/sagernet/gomobile v0.1.10/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= +github.com/sagernet/gomobile v0.1.11 h1:niMQAspvuThup5eRZQpsGcbM76zAvnsGr7RUIpnQMDQ= +github.com/sagernet/gomobile v0.1.11/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o= github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= From f196b7a5834bfcbe5b1efda824fc51b633495ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 7 Jan 2026 14:37:58 +0800 Subject: [PATCH 094/185] tailscale: Add system interface support --- docs/configuration/endpoint/tailscale.md | 32 +++- docs/configuration/endpoint/tailscale.zh.md | 32 +++- go.mod | 7 +- go.sum | 17 ++- option/tailscale.go | 3 + protocol/tailscale/endpoint.go | 108 +++++++++++++- protocol/tailscale/tun_device_unix.go | 156 ++++++++++++++++++++ protocol/tailscale/tun_device_windows.go | 117 +++++++++++++++ 8 files changed, 458 insertions(+), 14 deletions(-) create mode 100644 protocol/tailscale/tun_device_unix.go create mode 100644 protocol/tailscale/tun_device_windows.go diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md index 9ac6caf0..67f08fdd 100644 --- a/docs/configuration/endpoint/tailscale.md +++ b/docs/configuration/endpoint/tailscale.md @@ -4,8 +4,11 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_port](#relay_server_port) :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + :material-plus: [system_interface](#system_interface) + :material-plus: [system_interface_name](#system_interface_name) + :material-plus: [system_interface_mtu](#system_interface_mtu) !!! question "Since sing-box 1.12.0" @@ -27,8 +30,11 @@ icon: material/new-box "advertise_exit_node": false, "relay_server_port": 0, "relay_server_static_endpoints": [], + "system_interface": false, + "system_interface_name": "", + "system_interface_mtu": 0, "udp_timeout": "5m", - + ... // Dial Fields } ``` @@ -98,12 +104,34 @@ Indicates whether the node should advertise itself as an exit node. #### relay_server_port +!!! question "Since sing-box 1.13.0" + The port to listen on for incoming relay connections from other Tailscale nodes. #### relay_server_static_endpoints +!!! question "Since sing-box 1.13.0" + Static endpoints to advertise for the relay server. +#### system_interface + +!!! question "Since sing-box 1.13.0" + +Create a system TUN interface for Tailscale. + +#### system_interface_name + +!!! question "Since sing-box 1.13.0" + +Custom TUN interface name. By default, `tailscale` (or `utun` on macOS) will be used. + +#### system_interface_mtu + +!!! question "Since sing-box 1.13.0" + +Override the TUN MTU. By default, Tailscale's own MTU is used. + #### udp_timeout UDP NAT expiration time. diff --git a/docs/configuration/endpoint/tailscale.zh.md b/docs/configuration/endpoint/tailscale.zh.md index 44eb6284..936f2d71 100644 --- a/docs/configuration/endpoint/tailscale.zh.md +++ b/docs/configuration/endpoint/tailscale.zh.md @@ -4,8 +4,11 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_port](#relay_server_port) :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + :material-plus: [system_interface](#system_interface) + :material-plus: [system_interface_name](#system_interface_name) + :material-plus: [system_interface_mtu](#system_interface_mtu) !!! question "自 sing-box 1.12.0 起" @@ -27,6 +30,9 @@ icon: material/new-box "advertise_exit_node": false, "relay_server_port": 0, "relay_server_static_endpoints": [], + "system_interface": false, + "system_interface_name": "", + "system_interface_mtu": 0, "udp_timeout": "5m", ... // 拨号字段 @@ -97,12 +103,34 @@ icon: material/new-box #### relay_server_port +!!! question "自 sing-box 1.13.0 起" + 监听来自其他 Tailscale 节点的中继连接的端口。 #### relay_server_static_endpoints +!!! question "自 sing-box 1.13.0 起" + 为中继服务器通告的静态端点。 +#### system_interface + +!!! question "自 sing-box 1.13.0 起" + +为 Tailscale 创建系统 TUN 接口。 + +#### system_interface_name + +!!! question "自 sing-box 1.13.0 起" + +自定义 TUN 接口名。默认使用 `tailscale`(macOS 上为 `utun`)。 + +#### system_interface_mtu + +!!! question "自 sing-box 1.13.0 起" + +覆盖 TUN 的 MTU。默认使用 Tailscale 自己的 MTU。 + #### udp_timeout UDP NAT 过期时间。 @@ -115,4 +143,4 @@ UDP NAT 过期时间。 Tailscale 端点中的拨号字段仅控制它如何连接到控制平面,与实际连接无关。 -参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 \ No newline at end of file +参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。 diff --git a/go.mod b/go.mod index 90fdb401..8f407c9b 100644 --- a/go.mod +++ b/go.mod @@ -38,10 +38,10 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251230194736-a5db80d71081 + github.com/sagernet/sing-tun v0.8.0-beta.11.0.20260107060547-525f783d005b github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.4 + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.5 github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 @@ -68,6 +68,7 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/database64128/netx-go v0.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect @@ -85,6 +86,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect @@ -131,6 +133,7 @@ require ( github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect + github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect github.com/tidwall/gjson v1.18.0 // indirect diff --git a/go.sum b/go.sum index 26e745fd..e909f7bc 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= +github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= @@ -79,6 +81,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= +github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= @@ -216,14 +220,14 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251230194736-a5db80d71081 h1:ZFw+y1RIKasXENuy8jOYfwpyiKBh92HcSqzDFQLd7Yc= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251230194736-a5db80d71081/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20260107060547-525f783d005b h1:MqPEFejgxqechqBn1OkL+9JPW0W4AiGCI0Y1JhglIlQ= +github.com/sagernet/sing-tun v0.8.0-beta.11.0.20260107060547-525f783d005b/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.4 h1:p+9JllOL5Q2pj6bmP9gu+LdjyRg/XxHLTpMfuhuQsY4= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.4/go.mod h1:HZxL3asFIkcIJtHdnqsdcXsY6d+1iMtq0SPUlX17TGM= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.5 h1:nn9or1e5sTDXay/dfsB4E/A4jYaYdPVCXV8mME/maEc= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.5/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= @@ -244,6 +248,8 @@ github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPx github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= +github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= @@ -262,6 +268,7 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -315,6 +322,8 @@ golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwE golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/option/tailscale.go b/option/tailscale.go index f8220df3..661d91a3 100644 --- a/option/tailscale.go +++ b/option/tailscale.go @@ -24,6 +24,9 @@ type TailscaleEndpointOptions struct { AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` RelayServerPort *uint16 `json:"relay_server_port,omitempty"` RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"` + SystemInterface bool `json:"system_interface,omitempty"` + SystemInterfaceName string `json:"system_interface_name,omitempty"` + SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 3bdef142..1bd63e71 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -22,8 +22,6 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/header" "github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" - "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" - "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/common/dialer" @@ -49,8 +47,10 @@ import ( tsDNS "github.com/sagernet/tailscale/net/dns" "github.com/sagernet/tailscale/net/netmon" "github.com/sagernet/tailscale/net/tsaddr" + tsTUN "github.com/sagernet/tailscale/net/tstun" "github.com/sagernet/tailscale/tsnet" "github.com/sagernet/tailscale/types/ipproto" + "github.com/sagernet/tailscale/types/nettype" "github.com/sagernet/tailscale/version" "github.com/sagernet/tailscale/wgengine" "github.com/sagernet/tailscale/wgengine/filter" @@ -101,6 +101,51 @@ type Endpoint struct { relayServerStaticEndpoints []netip.AddrPort udpTimeout time.Duration + + systemInterface bool + systemInterfaceName string + systemInterfaceMTU uint32 + systemTun tun.Tun + fallbackTCPCloser func() +} + +func (t *Endpoint) registerNetstackHandlers() { + netstack := t.server.ExportNetstack() + if netstack == nil { + return + } + previousTCP := netstack.GetTCPHandlerForFlow + netstack.GetTCPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) { + if previousTCP != nil { + handler, intercept = previousTCP(src, dst) + if handler != nil || !intercept { + return handler, intercept + } + } + return func(conn net.Conn) { + ctx := log.ContextWithNewID(t.ctx) + source := M.SocksaddrFrom(src.Addr(), src.Port()) + destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) + t.NewConnectionEx(ctx, conn, source, destination, nil) + }, true + } + + previousUDP := netstack.GetUDPHandlerForFlow + netstack.GetUDPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(nettype.ConnPacketConn), intercept bool) { + if previousUDP != nil { + handler, intercept = previousUDP(src, dst) + if handler != nil || !intercept { + return handler, intercept + } + } + return func(conn nettype.ConnPacketConn) { + ctx := log.ContextWithNewID(t.ctx) + source := M.SocksaddrFrom(src.Addr(), src.Port()) + destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) + packetConn := bufio.NewPacketConn(conn) + t.NewPacketConnectionEx(ctx, packetConn, source, destination, nil) + }, true + } } func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) { @@ -202,6 +247,9 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL relayServerPort: options.RelayServerPort, relayServerStaticEndpoints: options.RelayServerStaticEndpoints, udpTimeout: udpTimeout, + systemInterface: options.SystemInterface, + systemInterfaceName: options.SystemInterfaceName, + systemInterfaceMTU: options.SystemInterfaceMTU, }, nil } @@ -237,10 +285,59 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { setAndroidProtectFunc(t.platformInterface) } } + if t.systemInterface { + mtu := t.systemInterfaceMTU + if mtu == 0 { + mtu = uint32(tsTUN.DefaultTUNMTU()) + } + tunName := t.systemInterfaceName + if tunName == "" { + tunName = tun.CalculateInterfaceName("tailscale") + } + tunOptions := tun.Options{ + Name: tunName, + MTU: mtu, + GSO: true, + InterfaceScope: true, + InterfaceMonitor: t.network.InterfaceMonitor(), + InterfaceFinder: t.network.InterfaceFinder(), + Logger: t.logger, + EXP_ExternalConfiguration: true, + } + systemTun, err := tun.New(tunOptions) + if err != nil { + return err + } + err = systemTun.Start() + if err != nil { + _ = systemTun.Close() + return err + } + wgTunDevice, err := newTunDeviceAdapter(systemTun, int(mtu), t.logger) + if err != nil { + _ = systemTun.Close() + return err + } + t.systemTun = systemTun + t.server.TunDevice = wgTunDevice + } err := t.server.Start() if err != nil { + if t.systemTun != nil { + _ = t.systemTun.Close() + } return err } + if t.fallbackTCPCloser == nil { + t.fallbackTCPCloser = t.server.RegisterFallbackTCPHandler(func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) { + return func(conn net.Conn) { + ctx := log.ContextWithNewID(t.ctx) + source := M.SocksaddrFrom(src.Addr(), src.Port()) + destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) + t.NewConnectionEx(ctx, conn, source, destination, nil) + }, true + }) + } t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig) ipStack := t.server.ExportNetstack().ExportIPStack() @@ -252,13 +349,12 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { if gErr != nil { return gonet.TranslateNetstackError(gErr) } - ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket) - ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout).HandlePacket) icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) t.stack = ipStack t.icmpForwarder = icmpForwarder + t.registerNetstackHandlers() localBackend := t.server.ExportLocalBackend() perfs := &ipn.MaskedPrefs{ @@ -355,6 +451,10 @@ func (t *Endpoint) Close() error { if runtime.GOOS == "android" { setAndroidProtectFunc(nil) } + if t.fallbackTCPCloser != nil { + t.fallbackTCPCloser() + t.fallbackTCPCloser = nil + } return common.Close(common.PtrOrNil(t.server)) } diff --git a/protocol/tailscale/tun_device_unix.go b/protocol/tailscale/tun_device_unix.go new file mode 100644 index 00000000..77f2955b --- /dev/null +++ b/protocol/tailscale/tun_device_unix.go @@ -0,0 +1,156 @@ +//go:build !windows + +package tailscale + +import ( + "encoding/hex" + "errors" + "io" + "os" + "sync" + "sync/atomic" + + singTun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" + wgTun "github.com/sagernet/wireguard-go/tun" +) + +type tunDeviceAdapter struct { + tun singTun.Tun + linuxTUN singTun.LinuxTUN + events chan wgTun.Event + mtu int + logger logger.ContextLogger + debugTun bool + readCount atomic.Uint32 + writeCount atomic.Uint32 + closeOnce sync.Once +} + +func newTunDeviceAdapter(tun singTun.Tun, mtu int, logger logger.ContextLogger) (wgTun.Device, error) { + if tun == nil { + return nil, os.ErrInvalid + } + if mtu == 0 { + mtu = 1500 + } + adapter := &tunDeviceAdapter{ + tun: tun, + events: make(chan wgTun.Event, 1), + mtu: mtu, + logger: logger, + debugTun: os.Getenv("SINGBOX_TS_TUN_DEBUG") != "", + } + if linuxTUN, ok := tun.(singTun.LinuxTUN); ok { + adapter.linuxTUN = linuxTUN + } + adapter.events <- wgTun.EventUp + return adapter, nil +} + +func (a *tunDeviceAdapter) File() *os.File { + return nil +} + +func (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { + if a.linuxTUN != nil { + n, err := a.linuxTUN.BatchRead(bufs, offset-singTun.PacketOffset, sizes) + if err == nil { + for i := 0; i < n; i++ { + a.debugPacket("read", bufs[i][offset:offset+sizes[i]]) + } + } + return n, err + } + if offset < singTun.PacketOffset { + return 0, io.ErrShortBuffer + } + readBuf := bufs[0][offset-singTun.PacketOffset:] + n, err := a.tun.Read(readBuf) + if err == nil { + if n < singTun.PacketOffset { + return 0, io.ErrUnexpectedEOF + } + sizes[0] = n - singTun.PacketOffset + a.debugPacket("read", readBuf[singTun.PacketOffset:n]) + return 1, nil + } + if errors.Is(err, singTun.ErrTooManySegments) { + err = wgTun.ErrTooManySegments + } + return 0, err +} + +func (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) { + if a.linuxTUN != nil { + for i := range bufs { + a.debugPacket("write", bufs[i][offset:]) + } + return a.linuxTUN.BatchWrite(bufs, offset) + } + for _, packet := range bufs { + a.debugPacket("write", packet[offset:]) + if singTun.PacketOffset > 0 { + common.ClearArray(packet[offset-singTun.PacketOffset : offset]) + singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:])) + } + _, err = a.tun.Write(packet[offset-singTun.PacketOffset:]) + if err != nil { + return 0, err + } + } + // WireGuard will not read count. + return 0, nil +} + +func (a *tunDeviceAdapter) MTU() (int, error) { + return a.mtu, nil +} + +func (a *tunDeviceAdapter) Name() (string, error) { + return a.tun.Name() +} + +func (a *tunDeviceAdapter) Events() <-chan wgTun.Event { + return a.events +} + +func (a *tunDeviceAdapter) Close() error { + var err error + a.closeOnce.Do(func() { + close(a.events) + err = a.tun.Close() + }) + return err +} + +func (a *tunDeviceAdapter) BatchSize() int { + if a.linuxTUN != nil { + return a.linuxTUN.BatchSize() + } + return 1 +} + +func (a *tunDeviceAdapter) debugPacket(direction string, packet []byte) { + if !a.debugTun || a.logger == nil { + return + } + var counter *atomic.Uint32 + switch direction { + case "read": + counter = &a.readCount + case "write": + counter = &a.writeCount + default: + return + } + if counter.Add(1) > 8 { + return + } + sample := packet + if len(sample) > 64 { + sample = sample[:64] + } + a.logger.Trace("tailscale tun ", direction, " len=", len(packet), " head=", hex.EncodeToString(sample)) +} diff --git a/protocol/tailscale/tun_device_windows.go b/protocol/tailscale/tun_device_windows.go new file mode 100644 index 00000000..3b0e3440 --- /dev/null +++ b/protocol/tailscale/tun_device_windows.go @@ -0,0 +1,117 @@ +//go:build windows + +package tailscale + +import ( + "errors" + "os" + "sync" + "sync/atomic" + + singTun "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/logger" + wgTun "github.com/sagernet/wireguard-go/tun" +) + +type tunDeviceAdapter struct { + tun singTun.WinTun + nativeTun *singTun.NativeTun + events chan wgTun.Event + mtu atomic.Int64 + closeOnce sync.Once +} + +func newTunDeviceAdapter(tun singTun.Tun, mtu int, _ logger.ContextLogger) (wgTun.Device, error) { + winTun, ok := tun.(singTun.WinTun) + if !ok { + return nil, errors.New("not a windows tun device") + } + nativeTun, ok := winTun.(*singTun.NativeTun) + if !ok { + return nil, errors.New("unsupported windows tun device") + } + if mtu == 0 { + mtu = 1500 + } + adapter := &tunDeviceAdapter{ + tun: winTun, + nativeTun: nativeTun, + events: make(chan wgTun.Event, 1), + } + adapter.mtu.Store(int64(mtu)) + adapter.events <- wgTun.EventUp + return adapter, nil +} + +func (a *tunDeviceAdapter) File() *os.File { + return nil +} + +func (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) { + packet, release, err := a.tun.ReadPacket() + if err != nil { + return 0, err + } + defer release() + sizes[0] = copy(bufs[0][offset-singTun.PacketOffset:], packet) + return 1, nil +} + +func (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) { + for _, packet := range bufs { + if singTun.PacketOffset > 0 { + singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:])) + } + _, err = a.tun.Write(packet[offset-singTun.PacketOffset:]) + if err != nil { + return 0, err + } + } + return 0, nil +} + +func (a *tunDeviceAdapter) MTU() (int, error) { + return int(a.mtu.Load()), nil +} + +func (a *tunDeviceAdapter) ForceMTU(mtu int) { + if mtu <= 0 { + return + } + update := int(a.mtu.Load()) != mtu + a.mtu.Store(int64(mtu)) + if update { + select { + case a.events <- wgTun.EventMTUUpdate: + default: + } + } +} + +func (a *tunDeviceAdapter) LUID() uint64 { + if a.nativeTun == nil { + return 0 + } + return a.nativeTun.LUID() +} + +func (a *tunDeviceAdapter) Name() (string, error) { + return a.tun.Name() +} + +func (a *tunDeviceAdapter) Events() <-chan wgTun.Event { + return a.events +} + +func (a *tunDeviceAdapter) Close() error { + var err error + a.closeOnce.Do(func() { + close(a.events) + err = a.tun.Close() + }) + return err +} + +func (a *tunDeviceAdapter) BatchSize() int { + return 1 +} From 6cd1eb9b941975033b1881c38dcd089551a1bf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 8 Jan 2026 14:28:10 +0800 Subject: [PATCH 095/185] Fix tailscale endpoint --- docs/configuration/endpoint/tailscale.md | 8 ++++---- docs/configuration/endpoint/tailscale.zh.md | 8 ++++---- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md index 67f08fdd..5ea89a0e 100644 --- a/docs/configuration/endpoint/tailscale.md +++ b/docs/configuration/endpoint/tailscale.md @@ -4,10 +4,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" - :material-plus: [relay_server_port](#relay_server_port) - :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) - :material-plus: [system_interface](#system_interface) - :material-plus: [system_interface_name](#system_interface_name) + :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + :material-plus: [system_interface](#system_interface) + :material-plus: [system_interface_name](#system_interface_name) :material-plus: [system_interface_mtu](#system_interface_mtu) !!! question "Since sing-box 1.12.0" diff --git a/docs/configuration/endpoint/tailscale.zh.md b/docs/configuration/endpoint/tailscale.zh.md index 936f2d71..1bd65878 100644 --- a/docs/configuration/endpoint/tailscale.zh.md +++ b/docs/configuration/endpoint/tailscale.zh.md @@ -4,10 +4,10 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" - :material-plus: [relay_server_port](#relay_server_port) - :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) - :material-plus: [system_interface](#system_interface) - :material-plus: [system_interface_name](#system_interface_name) + :material-plus: [relay_server_port](#relay_server_port) + :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) + :material-plus: [system_interface](#system_interface) + :material-plus: [system_interface_name](#system_interface_name) :material-plus: [system_interface_mtu](#system_interface_mtu) !!! question "自 sing-box 1.12.0 起" diff --git a/go.mod b/go.mod index 8f407c9b..fa22a031 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/sagernet/sing-tun v0.8.0-beta.11.0.20260107060547-525f783d005b github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.5 + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index e909f7bc..397174f7 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkV github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.5 h1:nn9or1e5sTDXay/dfsB4E/A4jYaYdPVCXV8mME/maEc= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.5/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= From 3890bd2be7028153287ceff1b22dd5960fc7f281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 8 Jan 2026 22:11:40 +0800 Subject: [PATCH 096/185] platform: Display k based bytes --- experimental/libbox/setup.go | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index b4b6ee8e..5063ce6d 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -71,11 +71,11 @@ func Version() string { } func FormatBytes(length int64) string { - return byteformats.FormatBytes(uint64(length)) + return byteformats.FormatKBytes(uint64(length)) } func FormatMemoryBytes(length int64) string { - return byteformats.FormatMemoryBytes(uint64(length)) + return byteformats.FormatMemoryKBytes(uint64(length)) } func FormatDuration(duration int64) string { diff --git a/go.mod b/go.mod index fa22a031..08e873af 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 - github.com/sagernet/sing v0.8.0-beta.8 + github.com/sagernet/sing v0.8.0-beta.9 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.8 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 397174f7..7f6c3ed9 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 h1:E9yZrU0ZxSiW5RrGUnFZeI02EIMdAAv0RxdoxXCqZyk= github.com/sagernet/quic-go v0.58.0-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.8 h1:hUo0wZ2HGTieV1flEIai96HFhF34mMHVnduRqJHQvxg= -github.com/sagernet/sing v0.8.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.9 h1:LTFjTYiEr6A5NZ04tbrjWLb0T7unSmEJX2ksJkfI1lY= +github.com/sagernet/sing v0.8.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.8 h1:Y0P8WTqWpfg80rLFsDfF22QumM+HEAjRQ2o+8Dv+vDs= From 8d88c6532fd58fb346598afe8c71b3cf548fcd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 9 Jan 2026 22:51:57 +0800 Subject: [PATCH 097/185] Add dial option `bind_address_no_port` --- common/dialer/default.go | 6 ++++++ docs/configuration/shared/dial.md | 16 +++++++++++++++- docs/configuration/shared/dial.zh.md | 16 +++++++++++++++- go.mod | 2 +- go.sum | 4 ++-- option/outbound.go | 1 + 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index d38ad487..3ab2e05a 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -137,6 +137,12 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath)) listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath)) } + if options.BindAddressNoPort { + if !C.IsLinux { + return nil, E.New("`bind_address_no_port` is only supported on Linux") + } + dialer.Control = control.Append(dialer.Control, control.BindAddressNoPort()) + } if options.ConnectTimeout != 0 { dialer.Timeout = time.Duration(options.ConnectTimeout) } else { diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 3907dcb2..306952fc 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) :material-plus: [tcp_keep_alive](#tcp_keep_alive) - :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + :material-plus: [bind_address_no_port](#bind_address_no_port) !!! quote "Changes in sing-box 1.12.0" @@ -29,6 +30,7 @@ icon: material/new-box "bind_interface": "", "inet4_bind_address": "", "inet6_bind_address": "", + "bind_address_no_port": false, "routing_mark": 0, "reuse_addr": false, "netns": "", @@ -76,6 +78,18 @@ The IPv4 address to bind to. The IPv6 address to bind to. +#### bind_address_no_port + +!!! question "Since sing-box 1.13.0" + +!!! quote "" + + Only supported on Linux. + +Do not reserve a port when binding to a source address. + +This allows reusing the same source port for multiple connections if the full 4-tuple (source IP, source port, destination IP, destination port) remains unique. + #### routing_mark !!! quote "" diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 23cea509..49309351 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [disable_tcp_keep_alive](#disable_tcp_keep_alive) :material-plus: [tcp_keep_alive](#tcp_keep_alive) - :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + :material-plus: [tcp_keep_alive_interval](#tcp_keep_alive_interval) + :material-plus: [bind_address_no_port](#bind_address_no_port) !!! quote "sing-box 1.12.0 中的更改" @@ -29,6 +30,7 @@ icon: material/new-box "bind_interface": "", "inet4_bind_address": "", "inet6_bind_address": "", + "bind_address_no_port": false, "routing_mark": 0, "reuse_addr": false, "netns": "", @@ -76,6 +78,18 @@ icon: material/new-box 要绑定的 IPv6 地址。 +#### bind_address_no_port + +!!! question "自 sing-box 1.13.0 起" + +!!! quote "" + + 仅支持 Linux。 + +绑定到源地址时不保留端口。 + +这允许在完整的四元组(源 IP、源端口、目标 IP、目标端口)保持唯一的情况下,为多个连接复用同一源端口。 + #### routing_mark !!! quote "" diff --git a/go.mod b/go.mod index 08e873af..7ba33b25 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 - github.com/sagernet/sing v0.8.0-beta.9 + github.com/sagernet/sing v0.8.0-beta.9.0.20260109141034-50e1ed36c6f6 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.8 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 7f6c3ed9..68913f00 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 h1:E9yZrU0ZxSiW5RrGUnFZeI02EIMdAAv0RxdoxXCqZyk= github.com/sagernet/quic-go v0.58.0-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.9 h1:LTFjTYiEr6A5NZ04tbrjWLb0T7unSmEJX2ksJkfI1lY= -github.com/sagernet/sing v0.8.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.9.0.20260109141034-50e1ed36c6f6 h1:TgmW2IKThK+3wWhoB/UKjwdW2NUyWqCyXZoCYTMizv8= +github.com/sagernet/sing v0.8.0-beta.9.0.20260109141034-50e1ed36c6f6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.8 h1:Y0P8WTqWpfg80rLFsDfF22QumM+HEAjRQ2o+8Dv+vDs= diff --git a/option/outbound.go b/option/outbound.go index b5c52fa2..2ed7cf2e 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -69,6 +69,7 @@ type DialerOptions struct { BindInterface string `json:"bind_interface,omitempty"` Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` + BindAddressNoPort bool `json:"bind_address_no_port,omitempty"` ProtectPath string `json:"protect_path,omitempty"` RoutingMark FwMark `json:"routing_mark,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"` From e0f1cdf464ac91778335c018bc67253c9e0ac57b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 11 Jan 2026 17:07:31 +0800 Subject: [PATCH 098/185] platform: Uniq network interfaces --- experimental/libbox/service.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 2d5d9be4..3a13f6d1 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -126,6 +126,9 @@ func (w *platformInterfaceWrapper) NetworkInterfaces() ([]adapter.NetworkInterfa Constrained: isDefault && w.isConstrained, }) } + interfaces = common.UniqBy(interfaces, func(it adapter.NetworkInterface) string { + return it.Name + }) return interfaces, nil } From e6c03fd448b515ca31005d44eab49ae6f01085b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 12 Jan 2026 08:12:26 +0800 Subject: [PATCH 099/185] Update quic-go to v0.59.0 --- go.mod | 6 +++--- go.sum | 12 ++++++------ test/naive_self_test.go | 2 ++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 7ba33b25..80357566 100644 --- a/go.mod +++ b/go.mod @@ -31,10 +31,10 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 - github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 - github.com/sagernet/sing v0.8.0-beta.9.0.20260109141034-50e1ed36c6f6 + github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 + github.com/sagernet/sing v0.8.0-beta.10 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.8 + github.com/sagernet/sing-quic v0.6.0-beta.10 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index 68913f00..2c59da06 100644 --- a/go.sum +++ b/go.sum @@ -206,14 +206,14 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 h1:E9yZrU0ZxSiW5RrGUnFZeI02EIMdAAv0RxdoxXCqZyk= -github.com/sagernet/quic-go v0.58.0-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.9.0.20260109141034-50e1ed36c6f6 h1:TgmW2IKThK+3wWhoB/UKjwdW2NUyWqCyXZoCYTMizv8= -github.com/sagernet/sing v0.8.0-beta.9.0.20260109141034-50e1ed36c6f6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/sing v0.8.0-beta.10 h1:xLTfDWM3CfLRQC7BZSR3LMvc6hNolYCPBEdSD2mZXa8= +github.com/sagernet/sing v0.8.0-beta.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.8 h1:Y0P8WTqWpfg80rLFsDfF22QumM+HEAjRQ2o+8Dv+vDs= -github.com/sagernet/sing-quic v0.6.0-beta.8/go.mod h1:Y3YVjPutLHLQvYjGtUFH+w8YmM4MTd19NDzxLZGNGIs= +github.com/sagernet/sing-quic v0.6.0-beta.10 h1:KvuRBJWT199ClTHtT2b/46vH7gGWHPJMDn+GumTFGQI= +github.com/sagernet/sing-quic v0.6.0-beta.10/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= diff --git a/test/naive_self_test.go b/test/naive_self_test.go index 873e7b9e..9d293bfb 100644 --- a/test/naive_self_test.go +++ b/test/naive_self_test.go @@ -1,3 +1,5 @@ +//go:build with_naive_outbound + package main import ( From ccf90aee8a1fb697e88d637505f6a75b4a283334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 12 Jan 2026 16:27:42 +0800 Subject: [PATCH 100/185] release: Improve publish_testflight --- Makefile | 5 +- cmd/internal/app_store_connect/main.go | 177 ++++++++++++------------- 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/Makefile b/Makefile index d5be4af9..22c2e77d 100644 --- a/Makefile +++ b/Makefile @@ -211,7 +211,7 @@ release_apple: lib_ios update_apple_version release_ios release_macos release_tv release_apple_beta: update_apple_version release_ios release_macos release_tvos publish_testflight: - go run -v ./cmd/internal/app_store_connect publish_testflight + go run -v ./cmd/internal/app_store_connect publish_testflight $(filter-out $@,$(MAKECMDGOALS)) prepare_app_store: go run -v ./cmd/internal/app_store_connect prepare_app_store @@ -269,3 +269,6 @@ update: git fetch git reset FETCH_HEAD --hard git clean -fdx + +%: + @: diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 1e7109b6..97919521 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -100,11 +100,32 @@ findVersion: } func publishTestflight(ctx context.Context) error { + if len(os.Args) < 3 { + return E.New("platform required: ios, macos, or tvos") + } + var platform asc.Platform + switch os.Args[2] { + case "ios": + platform = asc.PlatformIOS + case "macos": + platform = asc.PlatformMACOS + case "tvos": + platform = asc.PlatformTVOS + default: + return E.New("unknown platform: ", os.Args[2]) + } + tagVersion, err := build_shared.ReadTagVersion() if err != nil { return err } tag := tagVersion.VersionString() + + releaseNotes := F.ToString("sing-box ", tagVersion.String()) + if len(os.Args) >= 4 { + releaseNotes = strings.Join(os.Args[3:], " ") + } + client := createClient(20 * time.Minute) log.Info(tag, " list build IDs") @@ -115,97 +136,75 @@ func publishTestflight(ctx context.Context) error { buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string { return it.ID }) - var platforms []asc.Platform - if len(os.Args) == 3 { - switch os.Args[2] { - case "ios": - platforms = []asc.Platform{asc.PlatformIOS} - case "macos": - platforms = []asc.Platform{asc.PlatformMACOS} - case "tvos": - platforms = []asc.Platform{asc.PlatformTVOS} - default: - return E.New("unknown platform: ", os.Args[2]) - } - } else { - platforms = []asc.Platform{ - asc.PlatformIOS, - asc.PlatformMACOS, - asc.PlatformTVOS, - } - } + waitingForProcess := false - for _, platform := range platforms { - log.Info(string(platform), " list builds") - for { - builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ - FilterApp: []string{appID}, - FilterPreReleaseVersionPlatform: []string{string(platform)}, - }) - if err != nil { - return err - } - build := builds.Data[0] - if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) { - log.Info(string(platform), " ", tag, " waiting for process") - time.Sleep(15 * time.Second) - continue - } - if *build.Attributes.ProcessingState != "VALID" { - waitingForProcess = true - log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState) - time.Sleep(15 * time.Second) - continue - } - log.Info(string(platform), " ", tag, " list localizations") - localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil) - if err != nil { - return err - } - localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool { - return *it.Attributes.Locale == "en-US" - }) - if localization.ID == "" { - log.Fatal(string(platform), " ", tag, " no en-US localization found") - } - if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { - log.Info(string(platform), " ", tag, " update localization") - _, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr( - F.ToString("sing-box ", tagVersion.String()), - )) - if err != nil { - return err - } - } - log.Info(string(platform), " ", tag, " publish") - response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID}) - if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) { - log.Info("waiting for process") - time.Sleep(15 * time.Second) - continue - } else if err != nil { - return err - } - log.Info(string(platform), " ", tag, " list submissions") - betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{ - FilterBuild: []string{build.ID}, - }) - if err != nil { - return err - } - if len(betaSubmissions.Data) == 0 { - log.Info(string(platform), " ", tag, " create submission") - _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID) - if err != nil { - if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") { - log.Error(err) - break - } - return err - } - } - break + log.Info(string(platform), " list builds") + for { + builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ + FilterApp: []string{appID}, + FilterPreReleaseVersionPlatform: []string{string(platform)}, + }) + if err != nil { + return err } + build := builds.Data[0] + if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) { + log.Info(string(platform), " ", tag, " waiting for process") + time.Sleep(15 * time.Second) + continue + } + if *build.Attributes.ProcessingState != "VALID" { + waitingForProcess = true + log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState) + time.Sleep(15 * time.Second) + continue + } + log.Info(string(platform), " ", tag, " list localizations") + localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil) + if err != nil { + return err + } + localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool { + return *it.Attributes.Locale == "en-US" + }) + if localization.ID == "" { + log.Fatal(string(platform), " ", tag, " no en-US localization found") + } + if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { + log.Info(string(platform), " ", tag, " update localization") + _, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(releaseNotes)) + if err != nil { + return err + } + } + log.Info(string(platform), " ", tag, " publish") + response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID}) + if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) { + log.Info("waiting for process") + time.Sleep(15 * time.Second) + continue + } else if err != nil { + return err + } + log.Info(string(platform), " ", tag, " list submissions") + betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{ + FilterBuild: []string{build.ID}, + }) + if err != nil { + return err + } + if len(betaSubmissions.Data) == 0 { + log.Info(string(platform), " ", tag, " create submission") + _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID) + if err != nil { + if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") { + log.Error(err) + break + } + return err + } + } + break } return nil } From 30c3855e4bb1fea14acfc3e82dbab79030e7455e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 12 Jan 2026 20:44:01 +0800 Subject: [PATCH 101/185] Fix logic issues with BBR impl --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 80357566..9a092f8f 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 github.com/sagernet/sing v0.8.0-beta.10 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.10 + github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index 2c59da06..3ad717b7 100644 --- a/go.sum +++ b/go.sum @@ -212,8 +212,8 @@ github.com/sagernet/sing v0.8.0-beta.10 h1:xLTfDWM3CfLRQC7BZSR3LMvc6hNolYCPBEdSD github.com/sagernet/sing v0.8.0-beta.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.10 h1:KvuRBJWT199ClTHtT2b/46vH7gGWHPJMDn+GumTFGQI= -github.com/sagernet/sing-quic v0.6.0-beta.10/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= +github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= +github.com/sagernet/sing-quic v0.6.0-beta.11/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= From e8450b2e61c0ae698c984e2a27fb664dcc3df4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 14 Jan 2026 16:58:20 +0800 Subject: [PATCH 102/185] platform: Refactor CommandClient & Connections --- daemon/started_service.go | 297 ++++++++--- daemon/started_service.pb.go | 399 ++++++++------- daemon/started_service.proto | 28 +- daemon/started_service_grpc.pb.go | 60 +-- .../clashapi/trafficontrol/manager.go | 47 +- experimental/libbox/command_client.go | 470 ++++++++---------- experimental/libbox/command_types.go | 146 +++++- experimental/v2rayapi/stats_grpc.pb.go | 8 +- transport/v2raygrpc/stream_grpc.pb.go | 4 +- 9 files changed, 888 insertions(+), 571 deletions(-) diff --git a/daemon/started_service.go b/daemon/started_service.go index 7176f058..862b9920 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -56,6 +56,9 @@ type StartedService struct { urlTestHistoryStorage *urltest.HistoryStorage clashModeSubscriber *observable.Subscriber[struct{}] clashModeObserver *observable.Observer[struct{}] + + connectionEventSubscriber *observable.Subscriber[trafficontrol.ConnectionEvent] + connectionEventObserver *observable.Observer[trafficontrol.ConnectionEvent] } type ServiceOptions struct { @@ -83,17 +86,19 @@ func NewStartedService(options ServiceOptions) *StartedService { // userID: options.UserID, // groupID: options.GroupID, // systemProxyEnabled: options.SystemProxyEnabled, - serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE}, - serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4), - logSubscriber: observable.NewSubscriber[*log.Entry](128), - urlTestSubscriber: observable.NewSubscriber[struct{}](1), - urlTestHistoryStorage: urltest.NewHistoryStorage(), - clashModeSubscriber: observable.NewSubscriber[struct{}](1), + serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE}, + serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4), + logSubscriber: observable.NewSubscriber[*log.Entry](128), + urlTestSubscriber: observable.NewSubscriber[struct{}](1), + urlTestHistoryStorage: urltest.NewHistoryStorage(), + clashModeSubscriber: observable.NewSubscriber[struct{}](1), + connectionEventSubscriber: observable.NewSubscriber[trafficontrol.ConnectionEvent](256), } s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2) s.logObserver = observable.NewObserver(s.logSubscriber, 64) s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1) s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1) + s.connectionEventObserver = observable.NewObserver(s.connectionEventSubscriber, 64) return s } @@ -183,6 +188,7 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber) if instance.clashServer != nil { instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber) + instance.clashServer.(*clashapi.Server).TrafficManager().SetEventHook(s.connectionEventSubscriber) } s.serviceAccess.Unlock() err = instance.Start() @@ -666,7 +672,7 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set return nil, err } -func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error { +func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error { err := s.waitForStarted(server.Context()) if err != nil { return err @@ -674,69 +680,253 @@ func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsReque s.serviceAccess.RLock() boxService := s.instance s.serviceAccess.RUnlock() - ticker := time.NewTicker(time.Duration(request.Interval)) - defer ticker.Stop() + + if boxService.clashServer == nil { + return E.New("clash server not available") + } + trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager() - var ( - connections = make(map[uuid.UUID]*Connection) - outConnections []*Connection - ) + + subscription, done, err := s.connectionEventObserver.Subscribe() + if err != nil { + return err + } + defer s.connectionEventObserver.UnSubscribe(subscription) + + connectionSnapshots := make(map[uuid.UUID]connectionSnapshot) + initialEvents := s.buildInitialConnectionState(trafficManager, connectionSnapshots) + err = server.Send(&ConnectionEvents{ + Events: initialEvents, + Reset_: true, + }) + if err != nil { + return err + } + + interval := time.Duration(request.Interval) + if interval <= 0 { + interval = time.Second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { - outConnections = outConnections[:0] - for _, connection := range trafficManager.Connections() { - outConnections = append(outConnections, newConnection(connections, connection, false)) - } - for _, connection := range trafficManager.ClosedConnections() { - outConnections = append(outConnections, newConnection(connections, connection, true)) - } - err := server.Send(&Connections{Connections: outConnections}) - if err != nil { - return err - } select { case <-s.ctx.Done(): return s.ctx.Err() case <-server.Context().Done(): return server.Context().Err() + case <-done: + return nil + + case event := <-subscription: + var pendingEvents []*ConnectionEvent + if protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil { + pendingEvents = append(pendingEvents, protoEvent) + } + drain: + for { + select { + case event = <-subscription: + if protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil { + pendingEvents = append(pendingEvents, protoEvent) + } + default: + break drain + } + } + if len(pendingEvents) > 0 { + err = server.Send(&ConnectionEvents{Events: pendingEvents}) + if err != nil { + return err + } + } + case <-ticker.C: + protoEvents := s.buildTrafficUpdates(trafficManager, connectionSnapshots) + if len(protoEvents) == 0 { + continue + } + err = server.Send(&ConnectionEvents{Events: protoEvents}) + if err != nil { + return err + } } } } -func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) *Connection { - if oldConnection, loaded := connections[metadata.ID]; loaded { - if isClosed { - if oldConnection.ClosedAt == 0 { - oldConnection.Uplink = 0 - oldConnection.Downlink = 0 - oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli() - } - return oldConnection +type connectionSnapshot struct { + uplink int64 + downlink int64 + hadTraffic bool +} + +func (s *StartedService) buildInitialConnectionState(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent { + var events []*ConnectionEvent + + for _, metadata := range manager.Connections() { + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: metadata.ID.String(), + Connection: buildConnectionProto(metadata), + }) + snapshots[metadata.ID] = connectionSnapshot{ + uplink: metadata.Upload.Load(), + downlink: metadata.Download.Load(), } - lastUplink := oldConnection.UplinkTotal - lastDownlink := oldConnection.DownlinkTotal - uplinkTotal := metadata.Upload.Load() - downlinkTotal := metadata.Download.Load() - oldConnection.Uplink = uplinkTotal - lastUplink - oldConnection.Downlink = downlinkTotal - lastDownlink - oldConnection.UplinkTotal = uplinkTotal - oldConnection.DownlinkTotal = downlinkTotal - return oldConnection } + + for _, metadata := range manager.ClosedConnections() { + conn := buildConnectionProto(metadata) + conn.ClosedAt = metadata.ClosedAt.UnixMilli() + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: metadata.ID.String(), + Connection: conn, + }) + } + + return events +} + +func (s *StartedService) applyConnectionEvent(event trafficontrol.ConnectionEvent, snapshots map[uuid.UUID]connectionSnapshot) *ConnectionEvent { + switch event.Type { + case trafficontrol.ConnectionEventNew: + if _, exists := snapshots[event.ID]; exists { + return nil + } + snapshots[event.ID] = connectionSnapshot{ + uplink: event.Metadata.Upload.Load(), + downlink: event.Metadata.Download.Load(), + } + return &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: event.ID.String(), + Connection: buildConnectionProto(event.Metadata), + } + case trafficontrol.ConnectionEventClosed: + delete(snapshots, event.ID) + protoEvent := &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_CLOSED, + Id: event.ID.String(), + } + closedAt := event.ClosedAt + if closedAt.IsZero() && !event.Metadata.ClosedAt.IsZero() { + closedAt = event.Metadata.ClosedAt + } + if closedAt.IsZero() { + closedAt = time.Now() + } + protoEvent.ClosedAt = closedAt.UnixMilli() + if event.Metadata.ID != uuid.Nil { + conn := buildConnectionProto(event.Metadata) + conn.ClosedAt = protoEvent.ClosedAt + protoEvent.Connection = conn + } + return protoEvent + default: + return nil + } +} + +func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent { + activeConnections := manager.Connections() + activeIndex := make(map[uuid.UUID]trafficontrol.TrackerMetadata, len(activeConnections)) + var events []*ConnectionEvent + + for _, metadata := range activeConnections { + activeIndex[metadata.ID] = metadata + currentUpload := metadata.Upload.Load() + currentDownload := metadata.Download.Load() + snapshot, exists := snapshots[metadata.ID] + if !exists { + snapshots[metadata.ID] = connectionSnapshot{ + uplink: currentUpload, + downlink: currentDownload, + } + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_NEW, + Id: metadata.ID.String(), + Connection: buildConnectionProto(metadata), + }) + continue + } + uplinkDelta := currentUpload - snapshot.uplink + downlinkDelta := currentDownload - snapshot.downlink + if uplinkDelta < 0 || downlinkDelta < 0 { + snapshots[metadata.ID] = connectionSnapshot{ + uplink: currentUpload, + downlink: currentDownload, + } + continue + } + if uplinkDelta > 0 || downlinkDelta > 0 { + snapshots[metadata.ID] = connectionSnapshot{ + uplink: currentUpload, + downlink: currentDownload, + hadTraffic: true, + } + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, + Id: metadata.ID.String(), + UplinkDelta: uplinkDelta, + DownlinkDelta: downlinkDelta, + }) + continue + } + if snapshot.hadTraffic { + snapshots[metadata.ID] = connectionSnapshot{ + uplink: currentUpload, + downlink: currentDownload, + } + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, + Id: metadata.ID.String(), + UplinkDelta: 0, + DownlinkDelta: 0, + }) + } + } + + var closedIndex map[uuid.UUID]trafficontrol.TrackerMetadata + for id := range snapshots { + if _, exists := activeIndex[id]; exists { + continue + } + if closedIndex == nil { + closedIndex = make(map[uuid.UUID]trafficontrol.TrackerMetadata) + for _, metadata := range manager.ClosedConnections() { + closedIndex[metadata.ID] = metadata + } + } + closedAt := time.Now() + var conn *Connection + if metadata, ok := closedIndex[id]; ok { + if !metadata.ClosedAt.IsZero() { + closedAt = metadata.ClosedAt + } + conn = buildConnectionProto(metadata) + conn.ClosedAt = closedAt.UnixMilli() + } + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_CLOSED, + Id: id.String(), + ClosedAt: closedAt.UnixMilli(), + Connection: conn, + }) + delete(snapshots, id) + } + + return events +} + +func buildConnectionProto(metadata trafficontrol.TrackerMetadata) *Connection { var rule string if metadata.Rule != nil { rule = metadata.Rule.String() } uplinkTotal := metadata.Upload.Load() downlinkTotal := metadata.Download.Load() - uplink := uplinkTotal - downlink := downlinkTotal - var closedAt int64 - if !metadata.ClosedAt.IsZero() { - closedAt = metadata.ClosedAt.UnixMilli() - uplink = 0 - downlink = 0 - } var processInfo *ProcessInfo if metadata.Metadata.ProcessInfo != nil { processInfo = &ProcessInfo{ @@ -747,7 +937,7 @@ func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol PackageName: metadata.Metadata.ProcessInfo.AndroidPackageName, } } - connection := &Connection{ + return &Connection{ Id: metadata.ID.String(), Inbound: metadata.Metadata.Inbound, InboundType: metadata.Metadata.InboundType, @@ -760,9 +950,6 @@ func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol User: metadata.Metadata.User, FromOutbound: metadata.Metadata.Outbound, CreatedAt: metadata.CreatedAt.UnixMilli(), - ClosedAt: closedAt, - Uplink: uplink, - Downlink: downlink, UplinkTotal: uplinkTotal, DownlinkTotal: downlinkTotal, Rule: rule, @@ -771,8 +958,6 @@ func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol ChainList: metadata.Chain, ProcessInfo: processInfo, } - connections[metadata.ID] = connection - return connection } func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) { diff --git a/daemon/started_service.pb.go b/daemon/started_service.pb.go index b00a1fb2..ef9ea825 100644 --- a/daemon/started_service.pb.go +++ b/daemon/started_service.pb.go @@ -78,104 +78,55 @@ func (LogLevel) EnumDescriptor() ([]byte, []int) { return file_daemon_started_service_proto_rawDescGZIP(), []int{0} } -type ConnectionFilter int32 +type ConnectionEventType int32 const ( - ConnectionFilter_ALL ConnectionFilter = 0 - ConnectionFilter_ACTIVE ConnectionFilter = 1 - ConnectionFilter_CLOSED ConnectionFilter = 2 + ConnectionEventType_CONNECTION_EVENT_NEW ConnectionEventType = 0 + ConnectionEventType_CONNECTION_EVENT_UPDATE ConnectionEventType = 1 + ConnectionEventType_CONNECTION_EVENT_CLOSED ConnectionEventType = 2 ) -// Enum value maps for ConnectionFilter. +// Enum value maps for ConnectionEventType. var ( - ConnectionFilter_name = map[int32]string{ - 0: "ALL", - 1: "ACTIVE", - 2: "CLOSED", + ConnectionEventType_name = map[int32]string{ + 0: "CONNECTION_EVENT_NEW", + 1: "CONNECTION_EVENT_UPDATE", + 2: "CONNECTION_EVENT_CLOSED", } - ConnectionFilter_value = map[string]int32{ - "ALL": 0, - "ACTIVE": 1, - "CLOSED": 2, + ConnectionEventType_value = map[string]int32{ + "CONNECTION_EVENT_NEW": 0, + "CONNECTION_EVENT_UPDATE": 1, + "CONNECTION_EVENT_CLOSED": 2, } ) -func (x ConnectionFilter) Enum() *ConnectionFilter { - p := new(ConnectionFilter) +func (x ConnectionEventType) Enum() *ConnectionEventType { + p := new(ConnectionEventType) *p = x return p } -func (x ConnectionFilter) String() string { +func (x ConnectionEventType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (ConnectionFilter) Descriptor() protoreflect.EnumDescriptor { +func (ConnectionEventType) Descriptor() protoreflect.EnumDescriptor { return file_daemon_started_service_proto_enumTypes[1].Descriptor() } -func (ConnectionFilter) Type() protoreflect.EnumType { +func (ConnectionEventType) Type() protoreflect.EnumType { return &file_daemon_started_service_proto_enumTypes[1] } -func (x ConnectionFilter) Number() protoreflect.EnumNumber { +func (x ConnectionEventType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } -// Deprecated: Use ConnectionFilter.Descriptor instead. -func (ConnectionFilter) EnumDescriptor() ([]byte, []int) { +// Deprecated: Use ConnectionEventType.Descriptor instead. +func (ConnectionEventType) EnumDescriptor() ([]byte, []int) { return file_daemon_started_service_proto_rawDescGZIP(), []int{1} } -type ConnectionSortBy int32 - -const ( - ConnectionSortBy_DATE ConnectionSortBy = 0 - ConnectionSortBy_TRAFFIC ConnectionSortBy = 1 - ConnectionSortBy_TOTAL_TRAFFIC ConnectionSortBy = 2 -) - -// Enum value maps for ConnectionSortBy. -var ( - ConnectionSortBy_name = map[int32]string{ - 0: "DATE", - 1: "TRAFFIC", - 2: "TOTAL_TRAFFIC", - } - ConnectionSortBy_value = map[string]int32{ - "DATE": 0, - "TRAFFIC": 1, - "TOTAL_TRAFFIC": 2, - } -) - -func (x ConnectionSortBy) Enum() *ConnectionSortBy { - p := new(ConnectionSortBy) - *p = x - return p -} - -func (x ConnectionSortBy) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (ConnectionSortBy) Descriptor() protoreflect.EnumDescriptor { - return file_daemon_started_service_proto_enumTypes[2].Descriptor() -} - -func (ConnectionSortBy) Type() protoreflect.EnumType { - return &file_daemon_started_service_proto_enumTypes[2] -} - -func (x ConnectionSortBy) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use ConnectionSortBy.Descriptor instead. -func (ConnectionSortBy) EnumDescriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{2} -} - type ServiceStatus_Type int32 const ( @@ -215,11 +166,11 @@ func (x ServiceStatus_Type) String() string { } func (ServiceStatus_Type) Descriptor() protoreflect.EnumDescriptor { - return file_daemon_started_service_proto_enumTypes[3].Descriptor() + return file_daemon_started_service_proto_enumTypes[2].Descriptor() } func (ServiceStatus_Type) Type() protoreflect.EnumType { - return &file_daemon_started_service_proto_enumTypes[3] + return &file_daemon_started_service_proto_enumTypes[2] } func (x ServiceStatus_Type) Number() protoreflect.EnumNumber { @@ -1114,8 +1065,6 @@ func (x *SetSystemProxyEnabledRequest) GetEnabled() bool { type SubscribeConnectionsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"` - Filter ConnectionFilter `protobuf:"varint,2,opt,name=filter,proto3,enum=daemon.ConnectionFilter" json:"filter,omitempty"` - SortBy ConnectionSortBy `protobuf:"varint,3,opt,name=sortBy,proto3,enum=daemon.ConnectionSortBy" json:"sortBy,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1157,41 +1106,32 @@ func (x *SubscribeConnectionsRequest) GetInterval() int64 { return 0 } -func (x *SubscribeConnectionsRequest) GetFilter() ConnectionFilter { - if x != nil { - return x.Filter - } - return ConnectionFilter_ALL -} - -func (x *SubscribeConnectionsRequest) GetSortBy() ConnectionSortBy { - if x != nil { - return x.SortBy - } - return ConnectionSortBy_DATE -} - -type Connections struct { +type ConnectionEvent struct { state protoimpl.MessageState `protogen:"open.v1"` - Connections []*Connection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"` + Type ConnectionEventType `protobuf:"varint,1,opt,name=type,proto3,enum=daemon.ConnectionEventType" json:"type,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Connection *Connection `protobuf:"bytes,3,opt,name=connection,proto3" json:"connection,omitempty"` + UplinkDelta int64 `protobuf:"varint,4,opt,name=uplinkDelta,proto3" json:"uplinkDelta,omitempty"` + DownlinkDelta int64 `protobuf:"varint,5,opt,name=downlinkDelta,proto3" json:"downlinkDelta,omitempty"` + ClosedAt int64 `protobuf:"varint,6,opt,name=closedAt,proto3" json:"closedAt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *Connections) Reset() { - *x = Connections{} +func (x *ConnectionEvent) Reset() { + *x = ConnectionEvent{} mi := &file_daemon_started_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *Connections) String() string { +func (x *ConnectionEvent) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Connections) ProtoMessage() {} +func (*ConnectionEvent) ProtoMessage() {} -func (x *Connections) ProtoReflect() protoreflect.Message { +func (x *ConnectionEvent) ProtoReflect() protoreflect.Message { mi := &file_daemon_started_service_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1203,18 +1143,105 @@ func (x *Connections) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Connections.ProtoReflect.Descriptor instead. -func (*Connections) Descriptor() ([]byte, []int) { +// Deprecated: Use ConnectionEvent.ProtoReflect.Descriptor instead. +func (*ConnectionEvent) Descriptor() ([]byte, []int) { return file_daemon_started_service_proto_rawDescGZIP(), []int{17} } -func (x *Connections) GetConnections() []*Connection { +func (x *ConnectionEvent) GetType() ConnectionEventType { if x != nil { - return x.Connections + return x.Type + } + return ConnectionEventType_CONNECTION_EVENT_NEW +} + +func (x *ConnectionEvent) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ConnectionEvent) GetConnection() *Connection { + if x != nil { + return x.Connection } return nil } +func (x *ConnectionEvent) GetUplinkDelta() int64 { + if x != nil { + return x.UplinkDelta + } + return 0 +} + +func (x *ConnectionEvent) GetDownlinkDelta() int64 { + if x != nil { + return x.DownlinkDelta + } + return 0 +} + +func (x *ConnectionEvent) GetClosedAt() int64 { + if x != nil { + return x.ClosedAt + } + return 0 +} + +type ConnectionEvents struct { + state protoimpl.MessageState `protogen:"open.v1"` + Events []*ConnectionEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectionEvents) Reset() { + *x = ConnectionEvents{} + mi := &file_daemon_started_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectionEvents) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectionEvents) ProtoMessage() {} + +func (x *ConnectionEvents) ProtoReflect() protoreflect.Message { + mi := &file_daemon_started_service_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectionEvents.ProtoReflect.Descriptor instead. +func (*ConnectionEvents) Descriptor() ([]byte, []int) { + return file_daemon_started_service_proto_rawDescGZIP(), []int{18} +} + +func (x *ConnectionEvents) GetEvents() []*ConnectionEvent { + if x != nil { + return x.Events + } + return nil +} + +func (x *ConnectionEvents) GetReset_() bool { + if x != nil { + return x.Reset_ + } + return false +} + type Connection struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -1245,7 +1272,7 @@ type Connection struct { func (x *Connection) Reset() { *x = Connection{} - mi := &file_daemon_started_service_proto_msgTypes[18] + mi := &file_daemon_started_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1257,7 +1284,7 @@ func (x *Connection) String() string { func (*Connection) ProtoMessage() {} func (x *Connection) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[18] + mi := &file_daemon_started_service_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1270,7 +1297,7 @@ func (x *Connection) ProtoReflect() protoreflect.Message { // Deprecated: Use Connection.ProtoReflect.Descriptor instead. func (*Connection) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{18} + return file_daemon_started_service_proto_rawDescGZIP(), []int{19} } func (x *Connection) GetId() string { @@ -1440,7 +1467,7 @@ type ProcessInfo struct { func (x *ProcessInfo) Reset() { *x = ProcessInfo{} - mi := &file_daemon_started_service_proto_msgTypes[19] + mi := &file_daemon_started_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1452,7 +1479,7 @@ func (x *ProcessInfo) String() string { func (*ProcessInfo) ProtoMessage() {} func (x *ProcessInfo) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[19] + mi := &file_daemon_started_service_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1465,7 +1492,7 @@ func (x *ProcessInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead. func (*ProcessInfo) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{19} + return file_daemon_started_service_proto_rawDescGZIP(), []int{20} } func (x *ProcessInfo) GetProcessId() uint32 { @@ -1512,7 +1539,7 @@ type CloseConnectionRequest struct { func (x *CloseConnectionRequest) Reset() { *x = CloseConnectionRequest{} - mi := &file_daemon_started_service_proto_msgTypes[20] + mi := &file_daemon_started_service_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1524,7 +1551,7 @@ func (x *CloseConnectionRequest) String() string { func (*CloseConnectionRequest) ProtoMessage() {} func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[20] + mi := &file_daemon_started_service_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1537,7 +1564,7 @@ func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead. func (*CloseConnectionRequest) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{20} + return file_daemon_started_service_proto_rawDescGZIP(), []int{21} } func (x *CloseConnectionRequest) GetId() string { @@ -1556,7 +1583,7 @@ type DeprecatedWarnings struct { func (x *DeprecatedWarnings) Reset() { *x = DeprecatedWarnings{} - mi := &file_daemon_started_service_proto_msgTypes[21] + mi := &file_daemon_started_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1568,7 +1595,7 @@ func (x *DeprecatedWarnings) String() string { func (*DeprecatedWarnings) ProtoMessage() {} func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[21] + mi := &file_daemon_started_service_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1581,7 +1608,7 @@ func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message { // Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead. func (*DeprecatedWarnings) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{21} + return file_daemon_started_service_proto_rawDescGZIP(), []int{22} } func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning { @@ -1602,7 +1629,7 @@ type DeprecatedWarning struct { func (x *DeprecatedWarning) Reset() { *x = DeprecatedWarning{} - mi := &file_daemon_started_service_proto_msgTypes[22] + mi := &file_daemon_started_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1614,7 +1641,7 @@ func (x *DeprecatedWarning) String() string { func (*DeprecatedWarning) ProtoMessage() {} func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[22] + mi := &file_daemon_started_service_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1627,7 +1654,7 @@ func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message { // Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead. func (*DeprecatedWarning) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{22} + return file_daemon_started_service_proto_rawDescGZIP(), []int{23} } func (x *DeprecatedWarning) GetMessage() string { @@ -1660,7 +1687,7 @@ type StartedAt struct { func (x *StartedAt) Reset() { *x = StartedAt{} - mi := &file_daemon_started_service_proto_msgTypes[23] + mi := &file_daemon_started_service_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1672,7 +1699,7 @@ func (x *StartedAt) String() string { func (*StartedAt) ProtoMessage() {} func (x *StartedAt) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[23] + mi := &file_daemon_started_service_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1685,7 +1712,7 @@ func (x *StartedAt) ProtoReflect() protoreflect.Message { // Deprecated: Use StartedAt.ProtoReflect.Descriptor instead. func (*StartedAt) Descriptor() ([]byte, []int) { - return file_daemon_started_service_proto_rawDescGZIP(), []int{23} + return file_daemon_started_service_proto_rawDescGZIP(), []int{24} } func (x *StartedAt) GetStartedAt() int64 { @@ -1705,7 +1732,7 @@ type Log_Message struct { func (x *Log_Message) Reset() { *x = Log_Message{} - mi := &file_daemon_started_service_proto_msgTypes[24] + mi := &file_daemon_started_service_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1717,7 +1744,7 @@ func (x *Log_Message) String() string { func (*Log_Message) ProtoMessage() {} func (x *Log_Message) ProtoReflect() protoreflect.Message { - mi := &file_daemon_started_service_proto_msgTypes[24] + mi := &file_daemon_started_service_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1818,13 +1845,21 @@ const file_daemon_started_service_proto_rawDesc = "" + "\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" + "\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" + "\x1cSetSystemProxyEnabledRequest\x12\x18\n" + - "\aenabled\x18\x01 \x01(\bR\aenabled\"\x9d\x01\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\"9\n" + "\x1bSubscribeConnectionsRequest\x12\x1a\n" + - "\binterval\x18\x01 \x01(\x03R\binterval\x120\n" + - "\x06filter\x18\x02 \x01(\x0e2\x18.daemon.ConnectionFilterR\x06filter\x120\n" + - "\x06sortBy\x18\x03 \x01(\x0e2\x18.daemon.ConnectionSortByR\x06sortBy\"C\n" + - "\vConnections\x124\n" + - "\vconnections\x18\x01 \x03(\v2\x12.daemon.ConnectionR\vconnections\"\x95\x05\n" + + "\binterval\x18\x01 \x01(\x03R\binterval\"\xea\x01\n" + + "\x0fConnectionEvent\x12/\n" + + "\x04type\x18\x01 \x01(\x0e2\x1b.daemon.ConnectionEventTypeR\x04type\x12\x0e\n" + + "\x02id\x18\x02 \x01(\tR\x02id\x122\n" + + "\n" + + "connection\x18\x03 \x01(\v2\x12.daemon.ConnectionR\n" + + "connection\x12 \n" + + "\vuplinkDelta\x18\x04 \x01(\x03R\vuplinkDelta\x12$\n" + + "\rdownlinkDelta\x18\x05 \x01(\x03R\rdownlinkDelta\x12\x1a\n" + + "\bclosedAt\x18\x06 \x01(\x03R\bclosedAt\"Y\n" + + "\x10ConnectionEvents\x12/\n" + + "\x06events\x18\x01 \x03(\v2\x17.daemon.ConnectionEventR\x06events\x12\x14\n" + + "\x05reset\x18\x02 \x01(\bR\x05reset\"\x95\x05\n" + "\n" + "Connection\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + @@ -1873,17 +1908,11 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x04WARN\x10\x03\x12\b\n" + "\x04INFO\x10\x04\x12\t\n" + "\x05DEBUG\x10\x05\x12\t\n" + - "\x05TRACE\x10\x06*3\n" + - "\x10ConnectionFilter\x12\a\n" + - "\x03ALL\x10\x00\x12\n" + - "\n" + - "\x06ACTIVE\x10\x01\x12\n" + - "\n" + - "\x06CLOSED\x10\x02*<\n" + - "\x10ConnectionSortBy\x12\b\n" + - "\x04DATE\x10\x00\x12\v\n" + - "\aTRAFFIC\x10\x01\x12\x11\n" + - "\rTOTAL_TRAFFIC\x10\x022\xe0\v\n" + + "\x05TRACE\x10\x06*i\n" + + "\x13ConnectionEventType\x12\x18\n" + + "\x14CONNECTION_EVENT_NEW\x10\x00\x12\x1b\n" + + "\x17CONNECTION_EVENT_UPDATE\x10\x01\x12\x1b\n" + + "\x17CONNECTION_EVENT_CLOSED\x10\x022\xe5\v\n" + "\x0eStartedService\x12=\n" + "\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" + "\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" + @@ -1900,8 +1929,8 @@ const file_daemon_started_service_proto_rawDesc = "" + "\x0eSelectOutbound\x12\x1d.daemon.SelectOutboundRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" + "\x0eSetGroupExpand\x12\x1d.daemon.SetGroupExpandRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" + "\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" + - "\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12T\n" + - "\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x13.daemon.Connections\"\x000\x01\x12K\n" + + "\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Y\n" + + "\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x18.daemon.ConnectionEvents\"\x000\x01\x12K\n" + "\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" + "\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" + "\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12;\n" + @@ -1920,31 +1949,31 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte { } var ( - file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) - file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 25) + file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) + file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 26) file_daemon_started_service_proto_goTypes = []any{ (LogLevel)(0), // 0: daemon.LogLevel - (ConnectionFilter)(0), // 1: daemon.ConnectionFilter - (ConnectionSortBy)(0), // 2: daemon.ConnectionSortBy - (ServiceStatus_Type)(0), // 3: daemon.ServiceStatus.Type - (*ServiceStatus)(nil), // 4: daemon.ServiceStatus - (*ReloadServiceRequest)(nil), // 5: daemon.ReloadServiceRequest - (*SubscribeStatusRequest)(nil), // 6: daemon.SubscribeStatusRequest - (*Log)(nil), // 7: daemon.Log - (*DefaultLogLevel)(nil), // 8: daemon.DefaultLogLevel - (*Status)(nil), // 9: daemon.Status - (*Groups)(nil), // 10: daemon.Groups - (*Group)(nil), // 11: daemon.Group - (*GroupItem)(nil), // 12: daemon.GroupItem - (*URLTestRequest)(nil), // 13: daemon.URLTestRequest - (*SelectOutboundRequest)(nil), // 14: daemon.SelectOutboundRequest - (*SetGroupExpandRequest)(nil), // 15: daemon.SetGroupExpandRequest - (*ClashMode)(nil), // 16: daemon.ClashMode - (*ClashModeStatus)(nil), // 17: daemon.ClashModeStatus - (*SystemProxyStatus)(nil), // 18: daemon.SystemProxyStatus - (*SetSystemProxyEnabledRequest)(nil), // 19: daemon.SetSystemProxyEnabledRequest - (*SubscribeConnectionsRequest)(nil), // 20: daemon.SubscribeConnectionsRequest - (*Connections)(nil), // 21: daemon.Connections + (ConnectionEventType)(0), // 1: daemon.ConnectionEventType + (ServiceStatus_Type)(0), // 2: daemon.ServiceStatus.Type + (*ServiceStatus)(nil), // 3: daemon.ServiceStatus + (*ReloadServiceRequest)(nil), // 4: daemon.ReloadServiceRequest + (*SubscribeStatusRequest)(nil), // 5: daemon.SubscribeStatusRequest + (*Log)(nil), // 6: daemon.Log + (*DefaultLogLevel)(nil), // 7: daemon.DefaultLogLevel + (*Status)(nil), // 8: daemon.Status + (*Groups)(nil), // 9: daemon.Groups + (*Group)(nil), // 10: daemon.Group + (*GroupItem)(nil), // 11: daemon.GroupItem + (*URLTestRequest)(nil), // 12: daemon.URLTestRequest + (*SelectOutboundRequest)(nil), // 13: daemon.SelectOutboundRequest + (*SetGroupExpandRequest)(nil), // 14: daemon.SetGroupExpandRequest + (*ClashMode)(nil), // 15: daemon.ClashMode + (*ClashModeStatus)(nil), // 16: daemon.ClashModeStatus + (*SystemProxyStatus)(nil), // 17: daemon.SystemProxyStatus + (*SetSystemProxyEnabledRequest)(nil), // 18: daemon.SetSystemProxyEnabledRequest + (*SubscribeConnectionsRequest)(nil), // 19: daemon.SubscribeConnectionsRequest + (*ConnectionEvent)(nil), // 20: daemon.ConnectionEvent + (*ConnectionEvents)(nil), // 21: daemon.ConnectionEvents (*Connection)(nil), // 22: daemon.Connection (*ProcessInfo)(nil), // 23: daemon.ProcessInfo (*CloseConnectionRequest)(nil), // 24: daemon.CloseConnectionRequest @@ -1957,14 +1986,14 @@ var ( ) var file_daemon_started_service_proto_depIdxs = []int32{ - 3, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type + 2, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type 28, // 1: daemon.Log.messages:type_name -> daemon.Log.Message 0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel - 11, // 3: daemon.Groups.group:type_name -> daemon.Group - 12, // 4: daemon.Group.items:type_name -> daemon.GroupItem - 1, // 5: daemon.SubscribeConnectionsRequest.filter:type_name -> daemon.ConnectionFilter - 2, // 6: daemon.SubscribeConnectionsRequest.sortBy:type_name -> daemon.ConnectionSortBy - 22, // 7: daemon.Connections.connections:type_name -> daemon.Connection + 10, // 3: daemon.Groups.group:type_name -> daemon.Group + 11, // 4: daemon.Group.items:type_name -> daemon.GroupItem + 1, // 5: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType + 22, // 6: daemon.ConnectionEvent.connection:type_name -> daemon.Connection + 20, // 7: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent 23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo 26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning 0, // 10: daemon.Log.Message.level:type_name -> daemon.LogLevel @@ -1974,38 +2003,38 @@ var file_daemon_started_service_proto_depIdxs = []int32{ 29, // 14: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty 29, // 15: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty 29, // 16: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty - 6, // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest + 5, // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest 29, // 18: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty 29, // 19: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty 29, // 20: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty - 16, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode - 13, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest - 14, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest - 15, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest + 15, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode + 12, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest + 13, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest + 14, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest 29, // 25: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty - 19, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest - 20, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest + 18, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest + 19, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest 24, // 28: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest 29, // 29: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty 29, // 30: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty 29, // 31: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty 29, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty 29, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty - 4, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus - 7, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log - 8, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel + 3, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus + 6, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log + 7, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel 29, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty - 9, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status - 10, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups - 17, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus - 16, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode + 8, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status + 9, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups + 16, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus + 15, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode 29, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty 29, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty 29, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty 29, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty - 18, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus + 17, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus 29, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty - 21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections + 21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents 29, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty 29, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty 25, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings @@ -2027,8 +2056,8 @@ func file_daemon_started_service_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)), - NumEnums: 4, - NumMessages: 25, + NumEnums: 3, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/daemon/started_service.proto b/daemon/started_service.proto index cd501cb5..cc778f91 100644 --- a/daemon/started_service.proto +++ b/daemon/started_service.proto @@ -27,7 +27,7 @@ service StartedService { rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {} rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {} - rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream Connections) {} + rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {} rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {} rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {} rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {} @@ -143,24 +143,26 @@ message SetSystemProxyEnabledRequest { message SubscribeConnectionsRequest { int64 interval = 1; - ConnectionFilter filter = 2; - ConnectionSortBy sortBy = 3; } -enum ConnectionFilter { - ALL = 0; - ACTIVE = 1; - CLOSED = 2; +enum ConnectionEventType { + CONNECTION_EVENT_NEW = 0; + CONNECTION_EVENT_UPDATE = 1; + CONNECTION_EVENT_CLOSED = 2; } -enum ConnectionSortBy { - DATE = 0; - TRAFFIC = 1; - TOTAL_TRAFFIC = 2; +message ConnectionEvent { + ConnectionEventType type = 1; + string id = 2; + Connection connection = 3; + int64 uplinkDelta = 4; + int64 downlinkDelta = 5; + int64 closedAt = 6; } -message Connections { - repeated Connection connections = 1; +message ConnectionEvents { + repeated ConnectionEvent events = 1; + bool reset = 2; } message Connection { diff --git a/daemon/started_service_grpc.pb.go b/daemon/started_service_grpc.pb.go index 1fd09e40..438cca5c 100644 --- a/daemon/started_service_grpc.pb.go +++ b/daemon/started_service_grpc.pb.go @@ -58,7 +58,7 @@ type StartedServiceClient interface { SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) + SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) @@ -278,13 +278,13 @@ func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *Se return out, nil } -func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) { +func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &grpc.GenericClientStream[SubscribeConnectionsRequest, Connections]{ClientStream: stream} + x := &grpc.GenericClientStream[SubscribeConnectionsRequest, ConnectionEvents]{ClientStream: stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } @@ -295,7 +295,7 @@ func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *Sub } // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[Connections] +type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[ConnectionEvents] func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) @@ -357,7 +357,7 @@ type StartedServiceServer interface { SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) - SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error + SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) @@ -373,87 +373,87 @@ type StartedServiceServer interface { type UnimplementedStartedServiceServer struct{} func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method StopService not implemented") + return nil, status.Error(codes.Unimplemented, "method StopService not implemented") } func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method ReloadService not implemented") + return nil, status.Error(codes.Unimplemented, "method ReloadService not implemented") } func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error { - return status.Errorf(codes.Unimplemented, "method SubscribeServiceStatus not implemented") + return status.Error(codes.Unimplemented, "method SubscribeServiceStatus not implemented") } func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error { - return status.Errorf(codes.Unimplemented, "method SubscribeLog not implemented") + return status.Error(codes.Unimplemented, "method SubscribeLog not implemented") } func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented") + return nil, status.Error(codes.Unimplemented, "method GetDefaultLogLevel not implemented") } func (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method ClearLogs not implemented") + return nil, status.Error(codes.Unimplemented, "method ClearLogs not implemented") } func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error { - return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented") + return status.Error(codes.Unimplemented, "method SubscribeStatus not implemented") } func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error { - return status.Errorf(codes.Unimplemented, "method SubscribeGroups not implemented") + return status.Error(codes.Unimplemented, "method SubscribeGroups not implemented") } func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetClashModeStatus not implemented") + return nil, status.Error(codes.Unimplemented, "method GetClashModeStatus not implemented") } func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error { - return status.Errorf(codes.Unimplemented, "method SubscribeClashMode not implemented") + return status.Error(codes.Unimplemented, "method SubscribeClashMode not implemented") } func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetClashMode not implemented") + return nil, status.Error(codes.Unimplemented, "method SetClashMode not implemented") } func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method URLTest not implemented") + return nil, status.Error(codes.Unimplemented, "method URLTest not implemented") } func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SelectOutbound not implemented") + return nil, status.Error(codes.Unimplemented, "method SelectOutbound not implemented") } func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetGroupExpand not implemented") + return nil, status.Error(codes.Unimplemented, "method SetGroupExpand not implemented") } func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSystemProxyStatus not implemented") + return nil, status.Error(codes.Unimplemented, "method GetSystemProxyStatus not implemented") } func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetSystemProxyEnabled not implemented") + return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented") } -func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error { - return status.Errorf(codes.Unimplemented, "method SubscribeConnections not implemented") +func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error { + return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented") } func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method CloseConnection not implemented") + return nil, status.Error(codes.Unimplemented, "method CloseConnection not implemented") } func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method CloseAllConnections not implemented") + return nil, status.Error(codes.Unimplemented, "method CloseAllConnections not implemented") } func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented") + return nil, status.Error(codes.Unimplemented, "method GetDeprecatedWarnings not implemented") } func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetStartedAt not implemented") + return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented") } func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {} func (UnimplementedStartedServiceServer) testEmbeddedByValue() {} @@ -466,7 +466,7 @@ type UnsafeStartedServiceServer interface { } func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) { - // If the following call pancis, it indicates UnimplementedStartedServiceServer was + // If the following call panics, it indicates UnimplementedStartedServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -734,11 +734,11 @@ func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.S if err := stream.RecvMsg(m); err != nil { return err } - return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, Connections]{ServerStream: stream}) + return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, ConnectionEvents]{ServerStream: stream}) } // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[Connections] +type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[ConnectionEvents] func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CloseConnectionRequest) diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index bb4822df..45781c51 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -10,11 +10,29 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/x/list" "github.com/gofrs/uuid/v5" ) +type ConnectionEventType int + +const ( + ConnectionEventNew ConnectionEventType = iota + ConnectionEventUpdate + ConnectionEventClosed +) + +type ConnectionEvent struct { + Type ConnectionEventType + ID uuid.UUID + Metadata TrackerMetadata + UplinkDelta int64 + DownlinkDelta int64 + ClosedAt time.Time +} + type Manager struct { uploadTotal atomic.Int64 downloadTotal atomic.Int64 @@ -22,16 +40,29 @@ type Manager struct { connections compatible.Map[uuid.UUID, Tracker] closedConnectionsAccess sync.Mutex closedConnections list.List[TrackerMetadata] - // process *process.Process - memory uint64 + memory uint64 + + eventSubscriber *observable.Subscriber[ConnectionEvent] } func NewManager() *Manager { return &Manager{} } +func (m *Manager) SetEventHook(subscriber *observable.Subscriber[ConnectionEvent]) { + m.eventSubscriber = subscriber +} + func (m *Manager) Join(c Tracker) { - m.connections.Store(c.Metadata().ID, c) + metadata := c.Metadata() + m.connections.Store(metadata.ID, c) + if m.eventSubscriber != nil { + m.eventSubscriber.Emit(ConnectionEvent{ + Type: ConnectionEventNew, + ID: metadata.ID, + Metadata: metadata, + }) + } } func (m *Manager) Leave(c Tracker) { @@ -40,11 +71,19 @@ func (m *Manager) Leave(c Tracker) { if loaded { metadata.ClosedAt = time.Now() m.closedConnectionsAccess.Lock() - defer m.closedConnectionsAccess.Unlock() if m.closedConnections.Len() >= 1000 { m.closedConnections.PopFront() } m.closedConnections.PushBack(metadata) + m.closedConnectionsAccess.Unlock() + if m.eventSubscriber != nil { + m.eventSubscriber.Emit(ConnectionEvent{ + Type: ConnectionEventClosed, + ID: metadata.ID, + Metadata: metadata, + ClosedAt: metadata.ClosedAt, + }) + } } } diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 6f8b5acc..f0788d7a 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -27,6 +27,7 @@ type CommandClient struct { ctx context.Context cancel context.CancelFunc clientMutex sync.RWMutex + standalone bool } type CommandClientOptions struct { @@ -48,7 +49,7 @@ type CommandClientHandler interface { WriteGroups(message OutboundGroupIterator) InitializeClashMode(modeList StringIterator, currentMode string) UpdateClashMode(newMode string) - WriteConnections(message *Connections) + WriteConnectionEvents(events *ConnectionEvents) } type LogEntry struct { @@ -73,7 +74,7 @@ func SetXPCDialer(dialer XPCDialer) { } func NewStandaloneCommandClient() *CommandClient { - return new(CommandClient) + return &CommandClient{standalone: true} } func NewCommandClient(handler CommandClientHandler, options *CommandClientOptions) *CommandClient { @@ -97,147 +98,135 @@ func streamClientAuthInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc return streamer(ctx, desc, cc, method, opts...) } -func (c *CommandClient) grpcDial() (*grpc.ClientConn, error) { - var target string - if sCommandServerListenPort == 0 { - target = "unix://" + filepath.Join(sBasePath, "command.sock") - } else { - target = net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))) - } - var ( - conn *grpc.ClientConn - err error - ) - clientOptions := []grpc.DialOption{ - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), - grpc.WithStreamInterceptor(streamClientAuthInterceptor), - } - for i := 0; i < 10; i++ { - conn, err = grpc.NewClient(target, clientOptions...) - if err == nil { - return conn, nil +const ( + commandClientDialAttempts = 10 + commandClientDialBaseDelay = 100 * time.Millisecond + commandClientDialStepDelay = 50 * time.Millisecond +) + +func commandClientDialDelay(attempt int) time.Duration { + return commandClientDialBaseDelay + time.Duration(attempt)*commandClientDialStepDelay +} + +func dialTarget() (string, func(context.Context, string) (net.Conn, error)) { + if sXPCDialer != nil { + return "passthrough:///xpc", func(ctx context.Context, _ string) (net.Conn, error) { + fileDescriptor, err := sXPCDialer.DialXPC() + if err != nil { + return nil, err + } + return networkConnectionFromFileDescriptor(fileDescriptor) } - time.Sleep(time.Duration(100+i*50) * time.Millisecond) } - return nil, err + if sCommandServerListenPort == 0 { + return "unix://" + filepath.Join(sBasePath, "command.sock"), nil + } + return net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))), nil +} + +func networkConnectionFromFileDescriptor(fileDescriptor int32) (net.Conn, error) { + file := os.NewFile(uintptr(fileDescriptor), "xpc-command-socket") + if file == nil { + return nil, E.New("invalid file descriptor") + } + networkConnection, err := net.FileConn(file) + if err != nil { + file.Close() + return nil, E.Cause(err, "create connection from fd") + } + file.Close() + return networkConnection, nil +} + +func (c *CommandClient) dialWithRetry(target string, contextDialer func(context.Context, string) (net.Conn, error), retryDial bool) (*grpc.ClientConn, daemon.StartedServiceClient, error) { + var connection *grpc.ClientConn + var client daemon.StartedServiceClient + var lastError error + + for attempt := 0; attempt < commandClientDialAttempts; attempt++ { + if connection == nil { + options := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), + grpc.WithStreamInterceptor(streamClientAuthInterceptor), + } + if contextDialer != nil { + options = append(options, grpc.WithContextDialer(contextDialer)) + } + var err error + connection, err = grpc.NewClient(target, options...) + if err != nil { + lastError = err + if !retryDial { + return nil, nil, err + } + time.Sleep(commandClientDialDelay(attempt)) + continue + } + client = daemon.NewStartedServiceClient(connection) + } + waitDuration := commandClientDialDelay(attempt) + ctx, cancel := context.WithTimeout(context.Background(), waitDuration) + _, err := client.GetStartedAt(ctx, &emptypb.Empty{}, grpc.WaitForReady(true)) + cancel() + if err == nil { + return connection, client, nil + } + lastError = err + } + + if connection != nil { + connection.Close() + } + return nil, nil, lastError } func (c *CommandClient) Connect() error { c.clientMutex.Lock() common.Close(common.PtrOrNil(c.grpcConn)) - if sXPCDialer != nil { - fd, err := sXPCDialer.DialXPC() - if err != nil { - c.clientMutex.Unlock() - return err - } - file := os.NewFile(uintptr(fd), "xpc-command-socket") - if file == nil { - c.clientMutex.Unlock() - return E.New("invalid file descriptor") - } - netConn, err := net.FileConn(file) - if err != nil { - file.Close() - c.clientMutex.Unlock() - return E.Cause(err, "create connection from fd") - } - file.Close() - - clientOptions := []grpc.DialOption{ - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { - return netConn, nil - }), - grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), - grpc.WithStreamInterceptor(streamClientAuthInterceptor), - } - - grpcConn, err := grpc.NewClient("passthrough:///xpc", clientOptions...) - if err != nil { - netConn.Close() - c.clientMutex.Unlock() - return err - } - - c.grpcConn = grpcConn - c.grpcClient = daemon.NewStartedServiceClient(grpcConn) - c.ctx, c.cancel = context.WithCancel(context.Background()) - c.clientMutex.Unlock() - } else { - conn, err := c.grpcDial() - if err != nil { - c.clientMutex.Unlock() - return err - } - c.grpcConn = conn - c.grpcClient = daemon.NewStartedServiceClient(conn) - c.ctx, c.cancel = context.WithCancel(context.Background()) + target, contextDialer := dialTarget() + connection, client, err := c.dialWithRetry(target, contextDialer, true) + if err != nil { c.clientMutex.Unlock() + return err } + c.grpcConn = connection + c.grpcClient = client + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.clientMutex.Unlock() c.handler.Connected() - for _, command := range c.options.commands { - switch command { - case CommandLog: - go c.handleLogStream() - case CommandStatus: - go c.handleStatusStream() - case CommandGroup: - go c.handleGroupStream() - case CommandClashMode: - go c.handleClashModeStream() - case CommandConnections: - go c.handleConnectionsStream() - default: - return E.New("unknown command: ", command) - } - } - return nil + return c.dispatchCommands() } func (c *CommandClient) ConnectWithFD(fd int32) error { c.clientMutex.Lock() common.Close(common.PtrOrNil(c.grpcConn)) - file := os.NewFile(uintptr(fd), "xpc-command-socket") - if file == nil { - c.clientMutex.Unlock() - return E.New("invalid file descriptor") - } - - netConn, err := net.FileConn(file) + networkConnection, err := networkConnectionFromFileDescriptor(fd) if err != nil { - file.Close() - c.clientMutex.Unlock() - return E.Cause(err, "create connection from fd") - } - file.Close() - - clientOptions := []grpc.DialOption{ - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { - return netConn, nil - }), - grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), - grpc.WithStreamInterceptor(streamClientAuthInterceptor), - } - - grpcConn, err := grpc.NewClient("passthrough:///xpc", clientOptions...) - if err != nil { - netConn.Close() c.clientMutex.Unlock() return err } - - c.grpcConn = grpcConn - c.grpcClient = daemon.NewStartedServiceClient(grpcConn) + connection, client, err := c.dialWithRetry("passthrough:///xpc", func(ctx context.Context, _ string) (net.Conn, error) { + return networkConnection, nil + }, false) + if err != nil { + networkConnection.Close() + c.clientMutex.Unlock() + return err + } + c.grpcConn = connection + c.grpcClient = client c.ctx, c.cancel = context.WithCancel(context.Background()) c.clientMutex.Unlock() c.handler.Connected() + return c.dispatchCommands() +} + +func (c *CommandClient) dispatchCommands() error { for _, command := range c.options.commands { switch command { case CommandLog: @@ -281,57 +270,41 @@ func (c *CommandClient) getClientForCall() (daemon.StartedServiceClient, error) return c.grpcClient, nil } - if sXPCDialer != nil { - fd, err := sXPCDialer.DialXPC() - if err != nil { - return nil, err - } - file := os.NewFile(uintptr(fd), "xpc-command-socket") - if file == nil { - return nil, E.New("invalid file descriptor") - } - netConn, err := net.FileConn(file) - if err != nil { - file.Close() - return nil, E.Cause(err, "create connection from fd") - } - file.Close() - - clientOptions := []grpc.DialOption{ - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { - return netConn, nil - }), - grpc.WithUnaryInterceptor(unaryClientAuthInterceptor), - grpc.WithStreamInterceptor(streamClientAuthInterceptor), - } - - grpcConn, err := grpc.NewClient("passthrough:///xpc", clientOptions...) - if err != nil { - netConn.Close() - return nil, err - } - - c.grpcConn = grpcConn - c.grpcClient = daemon.NewStartedServiceClient(grpcConn) - if c.ctx == nil { - c.ctx, c.cancel = context.WithCancel(context.Background()) - } - return c.grpcClient, nil - } - - conn, err := c.grpcDial() + target, contextDialer := dialTarget() + connection, client, err := c.dialWithRetry(target, contextDialer, true) if err != nil { return nil, err } - c.grpcConn = conn - c.grpcClient = daemon.NewStartedServiceClient(conn) + c.grpcConn = connection + c.grpcClient = client if c.ctx == nil { c.ctx, c.cancel = context.WithCancel(context.Background()) } return c.grpcClient, nil } +func (c *CommandClient) closeConnection() { + c.clientMutex.Lock() + defer c.clientMutex.Unlock() + if c.grpcConn != nil { + c.grpcConn.Close() + c.grpcConn = nil + c.grpcClient = nil + } +} + +func callWithResult[T any](c *CommandClient, call func(client daemon.StartedServiceClient) (T, error)) (T, error) { + client, err := c.getClientForCall() + if err != nil { + var zero T + return zero, err + } + if c.standalone { + defer c.closeConnection() + } + return call(client) +} + func (c *CommandClient) getStreamContext() (daemon.StartedServiceClient, context.Context) { c.clientMutex.RLock() defer c.clientMutex.RUnlock() @@ -468,175 +441,134 @@ func (c *CommandClient) handleConnectionsStream() { return } - var connections Connections for { - conns, err := stream.Recv() + events, err := stream.Recv() if err != nil { c.handler.Disconnected(err.Error()) return } - connections.input = ConnectionsFromGRPC(conns) - c.handler.WriteConnections(&connections) + libboxEvents := ConnectionEventsFromGRPC(events) + c.handler.WriteConnectionEvents(libboxEvents) } } func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.SelectOutbound(context.Background(), &daemon.SelectOutboundRequest{ - GroupTag: groupTag, - OutboundTag: outboundTag, + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SelectOutbound(context.Background(), &daemon.SelectOutboundRequest{ + GroupTag: groupTag, + OutboundTag: outboundTag, + }) }) return err } func (c *CommandClient) URLTest(groupTag string) error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.URLTest(context.Background(), &daemon.URLTestRequest{ - OutboundTag: groupTag, + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.URLTest(context.Background(), &daemon.URLTestRequest{ + OutboundTag: groupTag, + }) }) return err } func (c *CommandClient) SetClashMode(newMode string) error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.SetClashMode(context.Background(), &daemon.ClashMode{ - Mode: newMode, + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SetClashMode(context.Background(), &daemon.ClashMode{ + Mode: newMode, + }) }) return err } func (c *CommandClient) CloseConnection(connId string) error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.CloseConnection(context.Background(), &daemon.CloseConnectionRequest{ - Id: connId, + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.CloseConnection(context.Background(), &daemon.CloseConnectionRequest{ + Id: connId, + }) }) return err } func (c *CommandClient) CloseConnections() error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.CloseAllConnections(context.Background(), &emptypb.Empty{}) + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.CloseAllConnections(context.Background(), &emptypb.Empty{}) + }) return err } func (c *CommandClient) ServiceReload() error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.ReloadService(context.Background(), &emptypb.Empty{}) + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.ReloadService(context.Background(), &emptypb.Empty{}) + }) return err } func (c *CommandClient) ServiceClose() error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.StopService(context.Background(), &emptypb.Empty{}) + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.StopService(context.Background(), &emptypb.Empty{}) + }) return err } func (c *CommandClient) ClearLogs() error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.ClearLogs(context.Background(), &emptypb.Empty{}) + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.ClearLogs(context.Background(), &emptypb.Empty{}) + }) return err } func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { - client, err := c.getClientForCall() - if err != nil { - return nil, err - } - - status, err := client.GetSystemProxyStatus(context.Background(), &emptypb.Empty{}) - if err != nil { - return nil, err - } - return SystemProxyStatusFromGRPC(status), nil + return callWithResult(c, func(client daemon.StartedServiceClient) (*SystemProxyStatus, error) { + status, err := client.GetSystemProxyStatus(context.Background(), &emptypb.Empty{}) + if err != nil { + return nil, err + } + return SystemProxyStatusFromGRPC(status), nil + }) } func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.SetSystemProxyEnabled(context.Background(), &daemon.SetSystemProxyEnabledRequest{ - Enabled: isEnabled, + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SetSystemProxyEnabled(context.Background(), &daemon.SetSystemProxyEnabledRequest{ + Enabled: isEnabled, + }) }) return err } func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) { - client, err := c.getClientForCall() - if err != nil { - return nil, err - } - - warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{}) - if err != nil { - return nil, err - } - - var notes []*DeprecatedNote - for _, warning := range warnings.Warnings { - notes = append(notes, &DeprecatedNote{ - Description: warning.Message, - MigrationLink: warning.MigrationLink, - }) - } - return newIterator(notes), nil + return callWithResult(c, func(client daemon.StartedServiceClient) (DeprecatedNoteIterator, error) { + warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{}) + if err != nil { + return nil, err + } + var notes []*DeprecatedNote + for _, warning := range warnings.Warnings { + notes = append(notes, &DeprecatedNote{ + Description: warning.Message, + MigrationLink: warning.MigrationLink, + }) + } + return newIterator(notes), nil + }) } func (c *CommandClient) GetStartedAt() (int64, error) { - client, err := c.getClientForCall() - if err != nil { - return 0, err - } - - startedAt, err := client.GetStartedAt(context.Background(), &emptypb.Empty{}) - if err != nil { - return 0, err - } - return startedAt.StartedAt, nil + return callWithResult(c, func(client daemon.StartedServiceClient) (int64, error) { + startedAt, err := client.GetStartedAt(context.Background(), &emptypb.Empty{}) + if err != nil { + return 0, err + } + return startedAt.StartedAt, nil + }) } func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error { - client, err := c.getClientForCall() - if err != nil { - return err - } - - _, err = client.SetGroupExpand(context.Background(), &daemon.SetGroupExpandRequest{ - GroupTag: groupTag, - IsExpand: isExpand, + _, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) { + return client.SetGroupExpand(context.Background(), &daemon.SetGroupExpandRequest{ + GroupTag: groupTag, + IsExpand: isExpand, + }) }) return err } diff --git a/experimental/libbox/command_types.go b/experimental/libbox/command_types.go index aa2fcdf2..1091b3f7 100644 --- a/experimental/libbox/command_types.go +++ b/experimental/libbox/command_types.go @@ -3,6 +3,7 @@ package libbox import ( "slices" "strings" + "time" "github.com/sagernet/sing-box/daemon" M "github.com/sagernet/sing/common/metadata" @@ -61,12 +62,119 @@ const ( ConnectionStateClosed ) +const ( + ConnectionEventNew = iota + ConnectionEventUpdate + ConnectionEventClosed +) + +const ( + closedConnectionMaxAge = int64((5 * time.Minute) / time.Millisecond) +) + +type ConnectionEvent struct { + Type int32 + ID string + Connection *Connection + UplinkDelta int64 + DownlinkDelta int64 + ClosedAt int64 +} + +type ConnectionEvents struct { + Reset bool + events []*ConnectionEvent +} + +func (c *ConnectionEvents) Iterator() ConnectionEventIterator { + return newIterator(c.events) +} + +type ConnectionEventIterator interface { + Next() *ConnectionEvent + HasNext() bool +} + type Connections struct { - input []Connection - filtered []Connection + connectionMap map[string]*Connection + input []Connection + filtered []Connection + filterState int32 + filterApplied bool +} + +func NewConnections() *Connections { + return &Connections{ + connectionMap: make(map[string]*Connection), + } +} + +func (c *Connections) ApplyEvents(events *ConnectionEvents) { + if events == nil { + return + } + if events.Reset { + c.connectionMap = make(map[string]*Connection) + } + + for _, event := range events.events { + switch event.Type { + case ConnectionEventNew: + if event.Connection != nil { + conn := *event.Connection + c.connectionMap[event.ID] = &conn + } + case ConnectionEventUpdate: + if conn, ok := c.connectionMap[event.ID]; ok { + conn.Uplink = event.UplinkDelta + conn.Downlink = event.DownlinkDelta + conn.UplinkTotal += event.UplinkDelta + conn.DownlinkTotal += event.DownlinkDelta + } + case ConnectionEventClosed: + if event.Connection != nil { + conn := *event.Connection + conn.ClosedAt = event.ClosedAt + conn.Uplink = 0 + conn.Downlink = 0 + c.connectionMap[event.ID] = &conn + continue + } + if conn, ok := c.connectionMap[event.ID]; ok { + conn.ClosedAt = event.ClosedAt + conn.Uplink = 0 + conn.Downlink = 0 + } + } + } + + c.evictClosedConnections(time.Now().UnixMilli()) + c.input = c.input[:0] + for _, conn := range c.connectionMap { + c.input = append(c.input, *conn) + } + if c.filterApplied { + c.FilterState(c.filterState) + } else { + c.filtered = c.filtered[:0] + c.filtered = append(c.filtered, c.input...) + } +} + +func (c *Connections) evictClosedConnections(nowMilliseconds int64) { + for id, conn := range c.connectionMap { + if conn.ClosedAt == 0 { + continue + } + if nowMilliseconds-conn.ClosedAt > closedConnectionMaxAge { + delete(c.connectionMap, id) + } + } } func (c *Connections) FilterState(state int32) { + c.filterApplied = true + c.filterState = state c.filtered = c.filtered[:0] switch state { case ConnectionStateAll: @@ -264,15 +372,37 @@ func ConnectionFromGRPC(conn *daemon.Connection) Connection { } } -func ConnectionsFromGRPC(connections *daemon.Connections) []Connection { - if connections == nil || len(connections.Connections) == 0 { +func ConnectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent { + if event == nil { return nil } - var libboxConnections []Connection - for _, conn := range connections.Connections { - libboxConnections = append(libboxConnections, ConnectionFromGRPC(conn)) + libboxEvent := &ConnectionEvent{ + Type: int32(event.Type), + ID: event.Id, + UplinkDelta: event.UplinkDelta, + DownlinkDelta: event.DownlinkDelta, + ClosedAt: event.ClosedAt, } - return libboxConnections + if event.Connection != nil { + conn := ConnectionFromGRPC(event.Connection) + libboxEvent.Connection = &conn + } + return libboxEvent +} + +func ConnectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents { + if events == nil { + return nil + } + libboxEvents := &ConnectionEvents{ + Reset: events.Reset_, + } + for _, event := range events.Events { + if libboxEvent := ConnectionEventFromGRPC(event); libboxEvent != nil { + libboxEvents.events = append(libboxEvents.events, libboxEvent) + } + } + return libboxEvents } func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus { diff --git a/experimental/v2rayapi/stats_grpc.pb.go b/experimental/v2rayapi/stats_grpc.pb.go index 3788f520..0745899f 100644 --- a/experimental/v2rayapi/stats_grpc.pb.go +++ b/experimental/v2rayapi/stats_grpc.pb.go @@ -84,15 +84,15 @@ type StatsServiceServer interface { type UnimplementedStatsServiceServer struct{} func (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetStats not implemented") + return nil, status.Error(codes.Unimplemented, "method GetStats not implemented") } func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented") + return nil, status.Error(codes.Unimplemented, "method QueryStats not implemented") } func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") + return nil, status.Error(codes.Unimplemented, "method GetSysStats not implemented") } func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} func (UnimplementedStatsServiceServer) testEmbeddedByValue() {} @@ -105,7 +105,7 @@ type UnsafeStatsServiceServer interface { } func RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) { - // If the following call pancis, it indicates UnimplementedStatsServiceServer was + // If the following call panics, it indicates UnimplementedStatsServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/transport/v2raygrpc/stream_grpc.pb.go b/transport/v2raygrpc/stream_grpc.pb.go index d602ec45..21cc3279 100644 --- a/transport/v2raygrpc/stream_grpc.pb.go +++ b/transport/v2raygrpc/stream_grpc.pb.go @@ -61,7 +61,7 @@ type GunServiceServer interface { type UnimplementedGunServiceServer struct{} func (UnimplementedGunServiceServer) Tun(grpc.BidiStreamingServer[Hunk, Hunk]) error { - return status.Errorf(codes.Unimplemented, "method Tun not implemented") + return status.Error(codes.Unimplemented, "method Tun not implemented") } func (UnimplementedGunServiceServer) mustEmbedUnimplementedGunServiceServer() {} func (UnimplementedGunServiceServer) testEmbeddedByValue() {} @@ -74,7 +74,7 @@ type UnsafeGunServiceServer interface { } func RegisterGunServiceServer(s grpc.ServiceRegistrar, srv GunServiceServer) { - // If the following call pancis, it indicates UnimplementedGunServiceServer was + // If the following call panics, it indicates UnimplementedGunServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. From 8ec58c96f552605841a6234e05c42dbb76cf424f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 17 Jan 2026 00:16:40 +0800 Subject: [PATCH 103/185] Fix naive outbound on iOS --- .github/CRONET_GO_VERSION | 2 +- .github/update_cronet.sh | 2 +- .github/update_cronet_dev.sh | 13 +++++ go.mod | 48 +++++++++--------- go.sum | 96 ++++++++++++++++++------------------ protocol/naive/outbound.go | 3 +- 6 files changed, 89 insertions(+), 75 deletions(-) create mode 100755 .github/update_cronet_dev.sh diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 6c59eb79..9063dff3 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -92d4602aba0ab6084673af0fe4887dccbc1049a5 +dc1cda1fe28740ba069934ab62aeb8ef85388332 diff --git a/.github/update_cronet.sh b/.github/update_cronet.sh index 0acee45e..17716b83 100755 --- a/.github/update_cronet.sh +++ b/.github/update_cronet.sh @@ -10,4 +10,4 @@ git -C $PROJECTS/cronet-go fetch origin go go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go) go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go) go mod tidy -git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$SCRIPT_DIR/CRONET_GO_VERSION" +git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION" diff --git a/.github/update_cronet_dev.sh b/.github/update_cronet_dev.sh new file mode 100755 index 00000000..13f7090c --- /dev/null +++ b/.github/update_cronet_dev.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +SCRIPT_DIR=$(dirname "$0") +PROJECTS=$SCRIPT_DIR/../.. + +git -C $PROJECTS/cronet-go fetch origin dev +git -C $PROJECTS/cronet-go fetch origin go_dev +go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev) +go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev) +go mod tidy +git -C $PROJECTS/cronet-go rev-parse origin/dev > "$SCRIPT_DIR/CRONET_GO_VERSION" diff --git a/go.mod b/go.mod index 9a092f8f..2fb64031 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251231120443-1d2d7341cbd8 - github.com/sagernet/cronet-go/all v0.0.0-20251231120443-1d2d7341cbd8 + github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 + github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -104,28 +104,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd80 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index 3ad717b7..68741633 100644 --- a/go.sum +++ b/go.sum @@ -148,54 +148,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251231120443-1d2d7341cbd8 h1:yQtAGNBF8c2cYdnsfkbOp8ShdWlpncua96KXqHd9svs= -github.com/sagernet/cronet-go v0.0.0-20251231120443-1d2d7341cbd8/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251231120443-1d2d7341cbd8 h1:KMmO1p8bO2BTq7NdTNdmjurh7rg6S1vRfHrEvFPw8bY= -github.com/sagernet/cronet-go/all v0.0.0-20251231120443-1d2d7341cbd8/go.mod h1:A4cCrezhvuNnb2K6UVuM+IQDZXBjTi2iK5K/wBu7Olg= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251231115934-b87a9ae9bd80 h1:9dBJpiOdU121TZ+mOb3LW8T5tZ3ZBjSUB9zg2F9Cltg= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:vODDxjRh7CHHnfoapv1+pDFtEMPOFxWnBlVSskXCOSU= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251231115934-b87a9ae9bd80 h1:ivqSFl9JHV63ELQkSTzeUkltLb+owOZ9GoRBTDYC0Jk= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:UKvOG4VuR+8c4VFA11d1dXZfANXNjDecXeZJfmjHTY4= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:qtw9lKXyuDG8kBB3PyhD/xidVhffST7caUEQ2fS6Sbo= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:Vkx2x+f67sOoL5K4YRX6RfIfM8z14YeGqj6Xc/CnzWg= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:+jfTH4yHr+toXrUH2xGUnhql641IIvbBTCKpz9WvTxU= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:Ow8A3f3YAgBJ4HMYJVv8RK0PsvQhAt4GHTBsiuaXJYw= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:HvlLfPq+WxRQtilZD4L3WgMYwZnwGnFoQX29eZtdm1Y= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251231115934-b87a9ae9bd80 h1:nGPM9VpMWhfPDX4SOk7p+GvHYAAv2LOCE4hNIue+eAY= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:GvFk6EH/PmIqxJN2OQarXt6vNrS/tgQ78b/WPvXhAmg= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:SaIqJcgT0D3fK4zdXRA3E9kzq4mtfwkNDzAnv6bFQjE= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:pEnLOmZjgWk9jaezJt/RvMX91Mugq4KwyxdW2mYvrUU= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251231115934-b87a9ae9bd80 h1:/HPZI7eocQxu7Ho5gBoOWEQwH+IgmnD0pG7RVQzeZK8= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:ZxVgKaU1r748EPp2y6GNZhMxtxxbC9xHSmWnJP4qDdM= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:v8cjiAvXjWOkC4Li05MQrQIJ1NH2vK2OWMc/mS/c7FI= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251231115934-b87a9ae9bd80 h1:Hh3at5gCCc2YZNbErv/jMqO4FhYN3oms892bLwK0aHI= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:Mz9JbTXKCfIvnvDxzID7YtkC/5Y/6MG1BWNBmOoTkaQ= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:xM0Z8k3WheuWfJsZNKEBkRyAFK/NEB01UNxElMf8oy8= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80 h1:Nvvip5xwIw9GoTg/Tch0uYW11wdc+VhZHTp81LuVZGY= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251231115934-b87a9ae9bd80 h1:BkrIlIUtKHtSXSZe2xGjgEtbP0z1LYo0PXeM25obSSM= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd80 h1:oJY5DO13q1x4XORhZYR7QVmYdFFhbua5w7Hp74h6Z10= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251231115934-b87a9ae9bd80/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 h1:0BYNmr0ptjsII948U0oBFmrbo4qEaCFcrE2JPRg3Zlk= +github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 h1:ghxhYSBQpzkakqWqJDvXr/Zmxe0WjTjKuALEGbjGiGY= +github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:M+4ZjPhLJXIvoxcQsbDofmc19Wrig59hZ+hLvj6S3To= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f h1:8jZbZ4KBTdcXDFLwUBNQt5Xci6ZuAKh255S8TwuBCaM= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f h1:tG0hCx+0u5zca7qQ7AMkcv4DCrBG/DKW1ggs/P+BRRI= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f h1:ZXp5hKJIA7iJ52ZShJCKMQEPLpp/7dDIVZmPGV9Il40= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f h1:gL7H8HS8s38adz4/HZtRHh79qMwsbLTRRPz4GQ9LcWI= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f h1:Dchgc0pAY5Jwb5lzUlE+1nhHIzqLx+YOurXLHgvWd/0= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f h1:+MOLSQoduuKDxF410i1LcSPaQGaiP0eZb0INvMlmjM4= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:lIZna05Vn6n8k21p8OpSUnhwGm+E57PrMjiI4ZUfMSg= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f h1:B2aFQ5CRHI20t8YsEizvtguS5W2QfK7D5XV/NzTIxPE= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:qpSwJ1rFGYCfJDenNCZoWYjoG7N+xEa6ke+E7/JO1i4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f h1:cx7Ipg0tSvTDjS4maMEYz4vuzz93BMPAysmZ1YLrz80= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f h1:4jOHuUiBxD8pJEpBBVQfJqyLmxjpd3t4MLRzU7YLFyg= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f h1:OpXBa2WlRU+Mam9oRe9Nn4/zf7gQ+qiBTNK8A5RwbfQ= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f h1:nJpGFi+6hI85tl4zoyNFEnFEQ5+xEV5gyvsUoMvd8g0= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f h1:SEy2rpmgOJgrqcEryJI/RSnqUWIsEsp0cfYoA8y21jc= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f h1:EW2TuFMLm0iBGqRZtuGwIZdeYmDtDsDmRcRRJQOMxUo= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f h1:3U5woxrNCkzfv1+UX+mVoWh1228AE1qAiMG02F9oFbY= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f h1:YwFTfuWG3mmctroeDYtFZ6LHjGsedVO+5wInYbbUuUY= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:r4V0ddPCRLgGu0VdgR3aUsO9NjpmyjAf+h+3oTD9D6E= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f h1:B8yf4gFvEYUnwWmtVK9sdwUsflYZ387MhYmlOP2ohFQ= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:9YyaMg4rO1/jIgrxmNb0LKH+X7frSYWfX2pFgW5JUVM= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f h1:B0fnGu0sh9yT/9JDN5u/GqThGoOzNN/daOAuGWFLXEk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f h1:lxPcIXKSSI5JDhc7rx/6yufISWM4vtBS2FY9PavWQTs= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.11 h1:niMQAspvuThup5eRZQpsGcbM76zAvnsGr7RUIpnQMDQ= diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index e78537bd..b2a43028 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -127,7 +127,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL var dnsResolver cronet.DNSResolverFunc if dnsRouter != nil { dnsResolver = func(dnsContext context.Context, request *mDNS.Msg) *mDNS.Msg { - response, err := dnsRouter.Exchange(dnsContext, request, adapter.DNSQueryOptions{}) + response, err := dnsRouter.Exchange(dnsContext, request, outboundDialer.(dialer.ResolveDialer).QueryOptions()) if err != nil { logger.Error("DNS exchange failed: ", err) return dns.FixedResponseStatus(request, mDNS.RcodeServerFailure) @@ -177,6 +177,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL } client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ Context: ctx, + Logger: logger, ServerAddress: serverAddress, ServerName: serverName, Username: options.Username, From 4a14d39cad508f8c87e0b3a492aa4fa052b58ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 17 Jan 2026 19:00:10 +0800 Subject: [PATCH 104/185] release: Log build ID during TestFlight publishing --- cmd/internal/app_store_connect/main.go | 1 + daemon/started_service.go | 39 +++++++++++-------- .../clashapi/trafficontrol/manager.go | 12 +++--- .../clashapi/trafficontrol/tracker.go | 10 ++--- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 97919521..d415abd6 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -148,6 +148,7 @@ func publishTestflight(ctx context.Context) error { return err } build := builds.Data[0] + log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")") if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) { log.Info(string(platform), " ", tag, " waiting for process") time.Sleep(15 * time.Second) diff --git a/daemon/started_service.go b/daemon/started_service.go index 862b9920..a42a752d 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -831,7 +831,7 @@ func (s *StartedService) applyConnectionEvent(event trafficontrol.ConnectionEven func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent { activeConnections := manager.Connections() - activeIndex := make(map[uuid.UUID]trafficontrol.TrackerMetadata, len(activeConnections)) + activeIndex := make(map[uuid.UUID]*trafficontrol.TrackerMetadata, len(activeConnections)) var events []*ConnectionEvent for _, metadata := range activeConnections { @@ -854,18 +854,25 @@ func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, sna uplinkDelta := currentUpload - snapshot.uplink downlinkDelta := currentDownload - snapshot.downlink if uplinkDelta < 0 || downlinkDelta < 0 { - snapshots[metadata.ID] = connectionSnapshot{ - uplink: currentUpload, - downlink: currentDownload, + if snapshot.hadTraffic { + events = append(events, &ConnectionEvent{ + Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, + Id: metadata.ID.String(), + UplinkDelta: 0, + DownlinkDelta: 0, + }) } + snapshot.uplink = currentUpload + snapshot.downlink = currentDownload + snapshot.hadTraffic = false + snapshots[metadata.ID] = snapshot continue } if uplinkDelta > 0 || downlinkDelta > 0 { - snapshots[metadata.ID] = connectionSnapshot{ - uplink: currentUpload, - downlink: currentDownload, - hadTraffic: true, - } + snapshot.uplink = currentUpload + snapshot.downlink = currentDownload + snapshot.hadTraffic = true + snapshots[metadata.ID] = snapshot events = append(events, &ConnectionEvent{ Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, Id: metadata.ID.String(), @@ -875,10 +882,10 @@ func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, sna continue } if snapshot.hadTraffic { - snapshots[metadata.ID] = connectionSnapshot{ - uplink: currentUpload, - downlink: currentDownload, - } + snapshot.uplink = currentUpload + snapshot.downlink = currentDownload + snapshot.hadTraffic = false + snapshots[metadata.ID] = snapshot events = append(events, &ConnectionEvent{ Type: ConnectionEventType_CONNECTION_EVENT_UPDATE, Id: metadata.ID.String(), @@ -888,13 +895,13 @@ func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, sna } } - var closedIndex map[uuid.UUID]trafficontrol.TrackerMetadata + var closedIndex map[uuid.UUID]*trafficontrol.TrackerMetadata for id := range snapshots { if _, exists := activeIndex[id]; exists { continue } if closedIndex == nil { - closedIndex = make(map[uuid.UUID]trafficontrol.TrackerMetadata) + closedIndex = make(map[uuid.UUID]*trafficontrol.TrackerMetadata) for _, metadata := range manager.ClosedConnections() { closedIndex[metadata.ID] = metadata } @@ -920,7 +927,7 @@ func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, sna return events } -func buildConnectionProto(metadata trafficontrol.TrackerMetadata) *Connection { +func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection { var rule string if metadata.Rule != nil { rule = metadata.Rule.String() diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index 45781c51..7bc8c347 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -27,7 +27,7 @@ const ( type ConnectionEvent struct { Type ConnectionEventType ID uuid.UUID - Metadata TrackerMetadata + Metadata *TrackerMetadata UplinkDelta int64 DownlinkDelta int64 ClosedAt time.Time @@ -39,7 +39,7 @@ type Manager struct { connections compatible.Map[uuid.UUID, Tracker] closedConnectionsAccess sync.Mutex - closedConnections list.List[TrackerMetadata] + closedConnections list.List[*TrackerMetadata] memory uint64 eventSubscriber *observable.Subscriber[ConnectionEvent] @@ -103,8 +103,8 @@ func (m *Manager) ConnectionsLen() int { return m.connections.Len() } -func (m *Manager) Connections() []TrackerMetadata { - var connections []TrackerMetadata +func (m *Manager) Connections() []*TrackerMetadata { + var connections []*TrackerMetadata m.connections.Range(func(_ uuid.UUID, value Tracker) bool { connections = append(connections, value.Metadata()) return true @@ -112,7 +112,7 @@ func (m *Manager) Connections() []TrackerMetadata { return connections } -func (m *Manager) ClosedConnections() []TrackerMetadata { +func (m *Manager) ClosedConnections() []*TrackerMetadata { m.closedConnectionsAccess.Lock() defer m.closedConnectionsAccess.Unlock() return m.closedConnections.Array() @@ -163,7 +163,7 @@ func (s *Snapshot) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]any{ "downloadTotal": s.Download, "uploadTotal": s.Upload, - "connections": common.Map(s.Connections, func(t Tracker) TrackerMetadata { return t.Metadata() }), + "connections": common.Map(s.Connections, func(t Tracker) *TrackerMetadata { return t.Metadata() }), "memory": s.Memory, }) } diff --git a/experimental/clashapi/trafficontrol/tracker.go b/experimental/clashapi/trafficontrol/tracker.go index 9cddc8cc..23500cd0 100644 --- a/experimental/clashapi/trafficontrol/tracker.go +++ b/experimental/clashapi/trafficontrol/tracker.go @@ -87,7 +87,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) { } type Tracker interface { - Metadata() TrackerMetadata + Metadata() *TrackerMetadata Close() error } @@ -97,8 +97,8 @@ type TCPConn struct { manager *Manager } -func (tt *TCPConn) Metadata() TrackerMetadata { - return tt.metadata +func (tt *TCPConn) Metadata() *TrackerMetadata { + return &tt.metadata } func (tt *TCPConn) Close() error { @@ -178,8 +178,8 @@ type UDPConn struct { manager *Manager } -func (ut *UDPConn) Metadata() TrackerMetadata { - return ut.metadata +func (ut *UDPConn) Metadata() *TrackerMetadata { + return &ut.metadata } func (ut *UDPConn) Close() error { From 725e4adc46e11e8dd0589bdbbcd50da977732e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 17 Jan 2026 06:47:38 +0800 Subject: [PATCH 105/185] release: Update android command --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 22c2e77d..5f1408b8 100644 --- a/Makefile +++ b/Makefile @@ -89,12 +89,12 @@ update_android_version: go run ./cmd/internal/update_android_version build_android: - cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop + cd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease :app:assembleOtherLegacyRelease && ./gradlew --stop upload_android: mkdir -p dist/release_android - cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android - cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android + cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android + cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android rm -rf dist/release_android From 490d50125742b54ffe2693d00a133e815f177379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 17 Jan 2026 19:15:40 +0800 Subject: [PATCH 106/185] Fix trafficontrol Manager --- .../clashapi/trafficontrol/manager.go | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index 7bc8c347..6763436d 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -33,13 +33,15 @@ type ConnectionEvent struct { ClosedAt time.Time } +const closedConnectionsLimit = 1000 + type Manager struct { uploadTotal atomic.Int64 downloadTotal atomic.Int64 connections compatible.Map[uuid.UUID, Tracker] closedConnectionsAccess sync.Mutex - closedConnections list.List[*TrackerMetadata] + closedConnections list.List[TrackerMetadata] memory uint64 eventSubscriber *observable.Subscriber[ConnectionEvent] @@ -69,19 +71,21 @@ func (m *Manager) Leave(c Tracker) { metadata := c.Metadata() _, loaded := m.connections.LoadAndDelete(metadata.ID) if loaded { - metadata.ClosedAt = time.Now() + closedAt := time.Now() + metadata.ClosedAt = closedAt + metadataCopy := *metadata m.closedConnectionsAccess.Lock() - if m.closedConnections.Len() >= 1000 { + if m.closedConnections.Len() >= closedConnectionsLimit { m.closedConnections.PopFront() } - m.closedConnections.PushBack(metadata) + m.closedConnections.PushBack(metadataCopy) m.closedConnectionsAccess.Unlock() if m.eventSubscriber != nil { m.eventSubscriber.Emit(ConnectionEvent{ Type: ConnectionEventClosed, ID: metadata.ID, - Metadata: metadata, - ClosedAt: metadata.ClosedAt, + Metadata: &metadataCopy, + ClosedAt: closedAt, }) } } @@ -114,8 +118,16 @@ func (m *Manager) Connections() []*TrackerMetadata { func (m *Manager) ClosedConnections() []*TrackerMetadata { m.closedConnectionsAccess.Lock() - defer m.closedConnectionsAccess.Unlock() - return m.closedConnections.Array() + values := m.closedConnections.Array() + m.closedConnectionsAccess.Unlock() + if len(values) == 0 { + return nil + } + connections := make([]*TrackerMetadata, len(values)) + for i := range values { + connections[i] = &values[i] + } + return connections } func (m *Manager) Connection(id uuid.UUID) Tracker { From b9cc87d35a19df6786e850db4d276c67715c74bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 17 Jan 2026 19:19:25 +0800 Subject: [PATCH 107/185] Skip strict routing in Windows versions below Windows 10 --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 2fb64031..942b91b1 100644 --- a/go.mod +++ b/go.mod @@ -32,13 +32,13 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 - github.com/sagernet/sing v0.8.0-beta.10 + github.com/sagernet/sing v0.8.0-beta.11 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.11.0.20260107060547-525f783d005b + github.com/sagernet/sing-tun v0.8.0-beta.13 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 diff --git a/go.sum b/go.sum index 68741633..c1a4b8c7 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.10 h1:xLTfDWM3CfLRQC7BZSR3LMvc6hNolYCPBEdSD2mZXa8= -github.com/sagernet/sing v0.8.0-beta.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.11 h1:nn/2Uod61b5rLHnXCuaFcbDxI1oRCodaxjzjt7Mobe4= +github.com/sagernet/sing v0.8.0-beta.11/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= @@ -220,8 +220,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20260107060547-525f783d005b h1:MqPEFejgxqechqBn1OkL+9JPW0W4AiGCI0Y1JhglIlQ= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20260107060547-525f783d005b/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.13 h1:vbI3uGthPIBU2lPOCbVK1YSLeV73ivV/olOUAvh1L2g= +github.com/sagernet/sing-tun v0.8.0-beta.13/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From 5d67c131fac58cb6be86b55c0f6bc552b205e796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 15 Dec 2025 13:07:57 +0800 Subject: [PATCH 108/185] documentation: Bump version --- docs/changelog.md | 276 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index a3a25d5c..e95fc298 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,23 @@ icon: material/alert-decagram --- +#### 1.13.0-beta.7 + +* Fixes and improvements + +#### 1.13.0-beta.6 + +* Update uTLS to v1.8.2 **1** +* Fixes and improvements + +**1**: + +This update fixes missing padding extension for Chrome 120+ fingerprints. + +Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities. +uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; +use NaiveProxy instead for TLS fingerprint resistance. + #### 1.12.17 * Update uTLS to v1.8.2 **1** @@ -15,18 +32,204 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; use NaiveProxy instead for TLS fingerprint resistance. +#### 1.13.0-beta.5 + +* Fixes and improvements + #### 1.12.16 * Fixes and improvements +#### 1.13.0-beta.4 + +* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs) +* Android: Add support for resisting VPN detection via Xposed +* Update quic-go to v0.59.0 +* Fixes and improvements + +#### 1.13.0-beta.2 + +* Add `bind_address_no_port` option for dial fields **1** +* Fixes and improvements + +**1**: + +Adds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address. + +This allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios. + +See [Dial Fields](/configuration/shared/dial/#bind_address_no_port). + +#### 1.13.0-beta.1 + +* Add system interface support for Tailscale endpoint **1** +* Fixes and improvements + +**1**: + +Tailscale endpoint can now create a system TUN interface to handle traffic directly. + +See [Tailscale endpoint](/configuration/endpoint/tailscale/#system_interface). + #### 1.12.15 * Fixes and improvements +#### 1.13.0-alpha.36 + +* Downgrade quic-go to v0.57.1 +* Fixes and improvements + +#### 1.13.0-alpha.35 + +* Add pre-match support for `auto_redirect` **1** +* Fixes and improvements + +**1**: + +`auto_redirect` now allows you to bypass sing-box for connections based on routing rules. + +A new rule action `bypass` is introduced to support this feature. When matched during pre-match, the connection will bypass sing-box and connect directly. + +This feature requires Linux with `auto_redirect` enabled. + +See [Pre-match](/configuration/shared/pre-match/) and [Rule Action](/configuration/route/rule_action/#bypass). + +#### 1.13.0-alpha.34 + +* Add Chrome Root Store certificate option **1** +* Add new options for ACME DNS-01 challenge providers **2** +* Add Wi-Fi state support for Linux and Windows **3** +* Update naiveproxy to 143.0.7499.109 +* Update quic-go to v0.58.0 +* Update tailscale to v1.92.4 +* Drop support for go1.23 **4** +* Drop support for Android 5.0 **5** + +**1**: + +Adds `chrome` as a new certificate store option alongside `mozilla`. +Both stores filter out China-based CA certificates. + +See [Certificate](/configuration/certificate/#store). + +**2**: + +See [DNS-01 Challenge](/configuration/shared/dns01_challenge/). + +**3**: + +sing-box can now monitor Wi-Fi state on Linux and Windows to enable routing rules based on `wifi_ssid` and `wifi_bssid`. + +See [Wi-Fi State](/configuration/shared/wifi-state/). + +**4**: + +Due to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile. + +**5**: + +Due to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0, +and only through a separate legacy build (with `-legacy-android-5` suffix). + +For standalone binaries, the minimum Android version has been raised to Android 6.0, +since Termux requires Android 7.0 or later. + #### 1.12.14 * Fixes and improvements +#### 1.13.0-alpha.33 + +* Fixes and improvements + +#### 1.13.0-alpha.32 + +* Remove `certificate_public_key_sha256` option for NaiveProxy outbound **1** +* Fixes and improvements + +**1**: + +Self-signed certificates change traffic behavior significantly, which defeats the purpose of NaiveProxy's design to resist traffic analysis. +For this reason, and due to maintenance costs, there is no reason to continue supporting `certificate_public_key_sha256`, which was designed to simplify the use of self-signed certificates. + +#### 1.13.0-alpha.31 + +* Add QUIC support for NaiveProxy outbound **1** +* Add QUIC congestion control option for NaiveProxy **2** +* Fixes and improvements + +**1**: + +NaiveProxy outbound now supports QUIC. + +See [NaiveProxy outbound](/configuration/outbound/naive/#quic). + +**2**: + +NaiveProxy inbound and outbound now supports configurable QUIC congestion control algorithms, including BBR and BBRv2. + +See [NaiveProxy inbound](/configuration/inbound/naive/#quic_congestion_control) and [NaiveProxy outbound](/configuration/outbound/naive/#quic_congestion_control). + +#### 1.13.0-alpha.30 + +* Add ECH support for NaiveProxy outbound **1** +* Add `tls.ech.query_server_name` option **2** +* Fix NaiveProxy outbound on Windows **3** +* Add OpenAI Codex Multiplexer service **4** +* Fixes and improvements + +**1**: + +See [NaiveProxy outbound](/configuration/outbound/naive/#tls). + +**2**: + +See [TLS](/configuration/shared/tls/#query_server_name). + +**3**: + +Each Windows release now includes `libcronet.dll`. +Ensure this file is in the same directory as `sing-box.exe` or in a directory listed in `PATH`. + +**4**: + +See [OCM](/configuration/service/ocm). + +#### 1.13.0-alpha.29 + +* Add UDP over TCP support for naiveproxy outbound **1** +* Fixes and improvements + +**1**: + +See [NaiveProxy outbound](/configuration/outbound/naive/#udp_over_tcp). + +#### 1.13.0-alpha.28 + +* Add naiveproxy outbound **1** +* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for dial fields **2** +* Update default TCP keep-alive initial period from 10 minutes to 5 minutes +* Update quic-go to v0.57.1 +* Fixes and improvements + +**1**: + +Only available on Apple platforms, Android, Windows and some Linux architectures. + +See [NaiveProxy outbound](/configuration/outbound/naive/). + +**2**: + +See [Dial Fields](/configuration/shared/dial/#tcp_keep_alive). + +* __Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client: +because system extensions require signatures to function, we have had to temporarily halt its release.__ + +__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then, +only clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__ + + #### 1.12.13 * Fix naive inbound @@ -42,10 +245,49 @@ only clients on TestFlight will be available (unless you have an Apple Developer * Fixes and improvements +#### 1.13.0-alpha.26 + +* Update quic-go to v0.55.0 +* Fix memory leak in hysteria2 +* Fixes and improvements + #### 1.12.11 * Fixes and improvements +#### 1.13.0-alpha.24 + +* Add Claude Code Multiplexer service **1** +* Fixes and improvements + +**1**: + +CCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients. + +See [CCM](/configuration/service/ccm). + +#### 1.13.0-alpha.23 + +* Fix compatibility with MPTCP **1** +* Fixes and improvements + +**1**: + +`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues, +but you can change it to bypass the sing-box via the new `exclude_mptcp` option. + +See [TUN](/configuration/inbound/tun/#exclude_mptcp). + +#### 1.13.0-alpha.22 + +* Update uTLS to v1.8.1 **1** +* Fixes and improvements + +**1**: + +This update fixes an critical issue that could cause simulated Chrome fingerprints to be detected, +see https://github.com/refraction-networking/utls/pull/375. + #### 1.12.10 * Update uTLS to v1.8.1 **1** @@ -56,18 +298,52 @@ only clients on TestFlight will be available (unless you have an Apple Developer This update fixes an critical issue that could cause simulated Chrome fingerprints to be detected, see https://github.com/refraction-networking/utls/pull/375. +#### 1.13.0-alpha.21 + +* Fix missing mTLS support in client options **1** +* Fixes and improvements + +See [TLS](/configuration/shared/tls/). + #### 1.12.9 * Fixes and improvements +#### 1.13.0-alpha.16 + +* Add curve preferences, pinned public key SHA256 and mTLS for TLS options **1** +* Fixes and improvements + +See [TLS](/configuration/shared/tls/). + +#### 1.13.0-alpha.15 + +* Update quic-go to v0.54.0 +* Update gVisor to v20250811 +* Update Tailscale to v1.86.5 +* Fixes and improvements + #### 1.12.8 * Fixes and improvements +#### 1.13.0-alpha.11 + +* Fixes and improvements + #### 1.12.5 * Fixes and improvements +#### 1.13.0-alpha.10 + +* Improve kTLS support **1** +* Fixes and improvements + +**1**: + +kTLS is now compatible with custom TLS implementations other than uTLS. + #### 1.12.4 * Fixes and improvements From 60a1e4c86600385257a9c0b4f4c1899fe6edde7b Mon Sep 17 00:00:00 2001 From: Balthild Date: Sat, 17 Jan 2026 06:52:43 -0600 Subject: [PATCH 109/185] Add acmedns support --- common/tls/acme.go | 8 ++++++++ constant/dns.go | 1 + docs/configuration/shared/dns01_challenge.md | 19 ++++++++++++++++++- .../shared/dns01_challenge.zh.md | 19 ++++++++++++++++++- go.mod | 1 + go.sum | 2 ++ option/tls_acme.go | 12 ++++++++++++ 7 files changed, 60 insertions(+), 2 deletions(-) diff --git a/common/tls/acme.go b/common/tls/acme.go index b8aef70f..c96e002c 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -14,6 +14,7 @@ import ( "github.com/sagernet/sing/common/logger" "github.com/caddyserver/certmagic" + "github.com/libdns/acmedns" "github.com/libdns/alidns" "github.com/libdns/cloudflare" "github.com/mholt/acmez/v3/acme" @@ -126,6 +127,13 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound APIToken: dnsOptions.CloudflareOptions.APIToken, ZoneToken: dnsOptions.CloudflareOptions.ZoneToken, } + case C.DNSProviderACMEDNS: + solver.DNSProvider = &acmedns.Provider{ + Username: dnsOptions.ACMEDNSOptions.Username, + Password: dnsOptions.ACMEDNSOptions.Password, + Subdomain: dnsOptions.ACMEDNSOptions.Subdomain, + ServerURL: dnsOptions.ACMEDNSOptions.ServerURL, + } default: return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider) } diff --git a/constant/dns.go b/constant/dns.go index 99e1ec0e..15d6096c 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -33,4 +33,5 @@ const ( const ( DNSProviderAliDNS = "alidns" DNSProviderCloudflare = "cloudflare" + DNSProviderACMEDNS = "acmedns" ) diff --git a/docs/configuration/shared/dns01_challenge.md b/docs/configuration/shared/dns01_challenge.md index 904803e6..8bdbfc97 100644 --- a/docs/configuration/shared/dns01_challenge.md +++ b/docs/configuration/shared/dns01_challenge.md @@ -5,7 +5,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.13.0" :material-plus: [alidns.security_token](#security_token) - :material-plus: [cloudflare.zone_token](#zone_token) + :material-plus: [cloudflare.zone_token](#zone_token) + :material-plus: [acmedns](#acmedns) ### Structure @@ -54,3 +55,19 @@ The Security Token for STS temporary credentials. Optional API token with `Zone:Read` permission. When provided, allows `api_token` to be scoped to a single zone. + +#### ACME-DNS + +!!! question "Since sing-box 1.13.0" + +```json +{ + "provider": "acmedns", + "username": "", + "password": "", + "subdomain": "", + "server_url": "" +} +``` + +See [ACME-DNS](https://github.com/joohoi/acme-dns) for details. diff --git a/docs/configuration/shared/dns01_challenge.zh.md b/docs/configuration/shared/dns01_challenge.zh.md index 7fb89c11..e6919338 100644 --- a/docs/configuration/shared/dns01_challenge.zh.md +++ b/docs/configuration/shared/dns01_challenge.zh.md @@ -5,7 +5,8 @@ icon: material/new-box !!! quote "sing-box 1.13.0 中的更改" :material-plus: [alidns.security_token](#security_token) - :material-plus: [cloudflare.zone_token](#zone_token) + :material-plus: [cloudflare.zone_token](#zone_token) + :material-plus: [acmedns](#acmedns) ### 结构 @@ -54,3 +55,19 @@ icon: material/new-box 具有 `Zone:Read` 权限的可选 API 令牌。 提供后可将 `api_token` 限定到单个区域。 + +#### ACME-DNS + +!!! question "自 sing-box 1.13.0 起" + +```json +{ + "provider": "acmedns", + "username": "", + "password": "", + "subdomain": "", + "server_url": "" +} +``` + +参阅 [ACME-DNS](https://github.com/joohoi/acme-dns)。 diff --git a/go.mod b/go.mod index 942b91b1..91c529ec 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/gofrs/uuid/v5 v5.4.0 github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 github.com/keybase/go-keychain v0.0.1 + github.com/libdns/acmedns v0.5.0 github.com/libdns/alidns v1.0.6-beta.3 github.com/libdns/cloudflare v0.2.2 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index c1a4b8c7..b2c41002 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE= +github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ= github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8= github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= diff --git a/option/tls_acme.go b/option/tls_acme.go index 1857c747..6dd8fa70 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -31,6 +31,7 @@ type _ACMEDNS01ChallengeOptions struct { Provider string `json:"provider,omitempty"` AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"` CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"` + ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"` } type ACMEDNS01ChallengeOptions _ACMEDNS01ChallengeOptions @@ -42,6 +43,8 @@ func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { v = o.AliDNSOptions case C.DNSProviderCloudflare: v = o.CloudflareOptions + case C.DNSProviderACMEDNS: + v = o.ACMEDNSOptions case "": return nil, E.New("missing provider type") default: @@ -61,6 +64,8 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { v = &o.AliDNSOptions case C.DNSProviderCloudflare: v = &o.CloudflareOptions + case C.DNSProviderACMEDNS: + v = &o.ACMEDNSOptions default: return E.New("unknown provider type: " + o.Provider) } @@ -82,3 +87,10 @@ type ACMEDNS01CloudflareOptions struct { APIToken string `json:"api_token,omitempty"` ZoneToken string `json:"zone_token,omitempty"` } + +type ACMEDNS01ACMEDNSOptions struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Subdomain string `json:"subdomain,omitempty"` + ServerURL string `json:"server_url,omitempty"` +} From 944a9986d9985f3aa79c529e3b3a3263d411315e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 28 Jan 2026 18:19:56 +0800 Subject: [PATCH 110/185] release: Always build tailscale for iOS and tvOS --- cmd/internal/build_libbox/main.go | 33 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index 62f364c4..e9cbb1ef 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -17,17 +17,17 @@ import ( ) var ( - debugEnabled bool - target string - platform string - withTailscale bool + debugEnabled bool + target string + platform string + // withTailscale bool ) func init() { flag.BoolVar(&debugEnabled, "debug", false, "enable debug") flag.StringVar(&target, "target", "android", "target platform") flag.StringVar(&platform, "platform", "", "specify platform") - flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS") + // flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS") } func main() { @@ -48,7 +48,7 @@ var ( debugFlags []string sharedTags []string darwinTags []string - memcTags []string + // memcTags []string notMemcTags []string debugTags []string ) @@ -64,8 +64,9 @@ func init() { debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0") - darwinTags = append(darwinTags, "with_dhcp") - memcTags = append(memcTags, "with_tailscale") + darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace") + // memcTags = append(memcTags, "with_tailscale") + sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird") notMemcTags = append(notMemcTags, "with_low_memory") debugTags = append(debugTags, "debug") } @@ -164,7 +165,7 @@ func buildAndroid() { // Build main variant (SDK 23) mainTags := append([]string{}, sharedTags...) - mainTags = append(mainTags, memcTags...) + // mainTags = append(mainTags, memcTags...) if debugEnabled { mainTags = append(mainTags, debugTags...) } @@ -176,7 +177,7 @@ func buildAndroid() { // Build legacy variant (SDK 21, no naive outbound) legacyTags := filterTags(sharedTags, "with_naive_outbound") - legacyTags = append(legacyTags, memcTags...) + // legacyTags = append(legacyTags, memcTags...) if debugEnabled { legacyTags = append(legacyTags, debugTags...) } @@ -204,9 +205,9 @@ func buildApple() { "-libname=box", "-tags-not-macos=with_low_memory", } - if !withTailscale { - args = append(args, "-tags-macos="+strings.Join(memcTags, ",")) - } + //if !withTailscale { + // args = append(args, "-tags-macos="+strings.Join(memcTags, ",")) + //} if !debugEnabled { args = append(args, sharedFlags...) @@ -215,9 +216,9 @@ func buildApple() { } tags := append(sharedTags, darwinTags...) - if withTailscale { - tags = append(tags, memcTags...) - } + //if withTailscale { + // tags = append(tags, memcTags...) + //} if debugEnabled { tags = append(tags, debugTags...) } From 1af14a0237949b4ed17f2c99193087ad1a4949cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 28 Jan 2026 18:22:28 +0800 Subject: [PATCH 111/185] Remove varbin usages --- adapter/experimental.go | 28 +- common/geosite/compat_test.go | 234 ++++++++++++ common/geosite/reader.go | 31 +- common/geosite/writer.go | 18 +- common/srs/binary.go | 71 +++- common/srs/compat_test.go | 494 ++++++++++++++++++++++++++ common/srs/ip_cidr.go | 19 +- common/srs/ip_set.go | 65 ++-- experimental/libbox/profile_import.go | 46 ++- go.mod | 2 +- go.sum | 4 +- 11 files changed, 953 insertions(+), 59 deletions(-) create mode 100644 common/geosite/compat_test.go create mode 100644 common/srs/compat_test.go diff --git a/adapter/experimental.go b/adapter/experimental.go index d4d37922..1bd8d2d9 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/binary" + "io" "time" "github.com/sagernet/sing/common/observable" @@ -68,7 +69,11 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } - err = varbin.Write(&buffer, binary.BigEndian, s.Content) + _, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content))) + if err != nil { + return nil, err + } + _, err = buffer.Write(s.Content) if err != nil { return nil, err } @@ -76,7 +81,11 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } - err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag) + _, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag))) + if err != nil { + return nil, err + } + _, err = buffer.WriteString(s.LastEtag) if err != nil { return nil, err } @@ -90,7 +99,12 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error { if err != nil { return err } - err = varbin.Read(reader, binary.BigEndian, &s.Content) + contentLength, err := binary.ReadUvarint(reader) + if err != nil { + return err + } + s.Content = make([]byte, contentLength) + _, err = io.ReadFull(reader, s.Content) if err != nil { return err } @@ -100,10 +114,16 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error { return err } s.LastUpdated = time.Unix(lastUpdated, 0) - err = varbin.Read(reader, binary.BigEndian, &s.LastEtag) + etagLength, err := binary.ReadUvarint(reader) if err != nil { return err } + etagBytes := make([]byte, etagLength) + _, err = io.ReadFull(reader, etagBytes) + if err != nil { + return err + } + s.LastEtag = string(etagBytes) return nil } diff --git a/common/geosite/compat_test.go b/common/geosite/compat_test.go new file mode 100644 index 00000000..1a55c644 --- /dev/null +++ b/common/geosite/compat_test.go @@ -0,0 +1,234 @@ +package geosite + +import ( + "bufio" + "bytes" + "encoding/binary" + "strings" + "testing" + + "github.com/sagernet/sing/common/varbin" + + "github.com/stretchr/testify/require" +) + +// Old implementation using varbin reflection-based serialization + +func oldWriteString(writer varbin.Writer, value string) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldWriteItem(writer varbin.Writer, item Item) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, item) +} + +func oldReadString(reader varbin.Reader) (string, error) { + //nolint:staticcheck + return varbin.ReadValue[string](reader, binary.BigEndian) +} + +func oldReadItem(reader varbin.Reader) (Item, error) { + //nolint:staticcheck + return varbin.ReadValue[Item](reader, binary.BigEndian) +} + +func TestStringCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input string + }{ + {"empty", ""}, + {"single_char", "a"}, + {"ascii", "example.com"}, + {"utf8", "测试域名.中国"}, + {"special_chars", "\x00\xff\n\t"}, + {"127_bytes", strings.Repeat("x", 127)}, + {"128_bytes", strings.Repeat("x", 128)}, + {"16383_bytes", strings.Repeat("x", 16383)}, + {"16384_bytes", strings.Repeat("x", 16384)}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteString(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = writeString(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadString(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack2) + }) + } +} + +func TestItemCompat(t *testing.T) { + t.Parallel() + + // Note: varbin.Write has a bug where struct values (not pointers) don't write their fields + // because field.CanSet() returns false for non-addressable values. + // The old geosite code passed Item values to varbin.Write, which silently wrote nothing. + // The new code correctly writes Type + Value using manual serialization. + // This test verifies the new serialization format and round-trip correctness. + + cases := []struct { + name string + input Item + }{ + {"domain_empty", Item{Type: RuleTypeDomain, Value: ""}}, + {"domain_normal", Item{Type: RuleTypeDomain, Value: "example.com"}}, + {"domain_suffix", Item{Type: RuleTypeDomainSuffix, Value: ".example.com"}}, + {"domain_keyword", Item{Type: RuleTypeDomainKeyword, Value: "google"}}, + {"domain_regex", Item{Type: RuleTypeDomainRegex, Value: `^.*\.example\.com$`}}, + {"utf8_domain", Item{Type: RuleTypeDomain, Value: "测试.com"}}, + {"long_domain", Item{Type: RuleTypeDomainSuffix, Value: strings.Repeat("a", 200) + ".com"}}, + {"128_bytes_value", Item{Type: RuleTypeDomain, Value: strings.Repeat("x", 128)}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // New write + var newBuf bytes.Buffer + err := newBuf.WriteByte(byte(tc.input.Type)) + require.NoError(t, err) + err = writeString(&newBuf, tc.input.Value) + require.NoError(t, err) + + // Verify format: Type (1 byte) + Value (uvarint len + bytes) + require.True(t, len(newBuf.Bytes()) >= 1, "output too short") + require.Equal(t, byte(tc.input.Type), newBuf.Bytes()[0], "type byte mismatch") + + // New write -> old read (varbin can read correctly when given addressable target) + readBack, err := oldReadItem(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack) + + // New write -> new read + reader := bufio.NewReader(bytes.NewReader(newBuf.Bytes())) + typeByte, err := reader.ReadByte() + require.NoError(t, err) + value, err := readString(reader) + require.NoError(t, err) + require.Equal(t, tc.input, Item{Type: ItemType(typeByte), Value: value}) + }) + } +} + +func TestGeositeWriteReadCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input map[string][]Item + }{ + { + "empty_map", + map[string][]Item{}, + }, + { + "single_code_empty_items", + map[string][]Item{"test": {}}, + }, + { + "single_code_single_item", + map[string][]Item{"test": {{Type: RuleTypeDomain, Value: "a.com"}}}, + }, + { + "single_code_multi_items", + map[string][]Item{ + "test": { + {Type: RuleTypeDomain, Value: "a.com"}, + {Type: RuleTypeDomainSuffix, Value: ".b.com"}, + {Type: RuleTypeDomainKeyword, Value: "keyword"}, + {Type: RuleTypeDomainRegex, Value: `^.*$`}, + }, + }, + }, + { + "multi_code", + map[string][]Item{ + "cn": {{Type: RuleTypeDomain, Value: "baidu.com"}, {Type: RuleTypeDomainSuffix, Value: ".cn"}}, + "us": {{Type: RuleTypeDomain, Value: "google.com"}}, + "jp": {{Type: RuleTypeDomainSuffix, Value: ".jp"}}, + }, + }, + { + "utf8_values", + map[string][]Item{ + "test": { + {Type: RuleTypeDomain, Value: "测试.中国"}, + {Type: RuleTypeDomainSuffix, Value: ".テスト"}, + }, + }, + }, + { + "large_items", + generateLargeItems(1000), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Write using new implementation + var buf bytes.Buffer + err := Write(&buf, tc.input) + require.NoError(t, err) + + // Read back and verify + reader, codes, err := NewReader(bytes.NewReader(buf.Bytes())) + require.NoError(t, err) + + // Verify all codes exist + codeSet := make(map[string]bool) + for _, code := range codes { + codeSet[code] = true + } + for code := range tc.input { + require.True(t, codeSet[code], "missing code: %s", code) + } + + // Verify items match + for code, expectedItems := range tc.input { + items, err := reader.Read(code) + require.NoError(t, err) + require.Equal(t, expectedItems, items, "items mismatch for code: %s", code) + } + }) + } +} + +func generateLargeItems(count int) map[string][]Item { + items := make([]Item, count) + for i := 0; i < count; i++ { + items[i] = Item{ + Type: ItemType(i % 4), + Value: strings.Repeat("x", i%200) + ".com", + } + } + return map[string][]Item{"large": items} +} diff --git a/common/geosite/reader.go b/common/geosite/reader.go index 3b3f7fec..ef99837d 100644 --- a/common/geosite/reader.go +++ b/common/geosite/reader.go @@ -9,7 +9,6 @@ import ( "sync/atomic" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/varbin" ) type Reader struct { @@ -78,7 +77,7 @@ func (r *Reader) readMetadata() error { codeIndex uint64 codeLength uint64 ) - code, err = varbin.ReadValue[string](reader, binary.BigEndian) + code, err = readString(reader) if err != nil { return err } @@ -112,9 +111,16 @@ func (r *Reader) Read(code string) ([]Item, error) { } r.bufferedReader.Reset(r.reader) itemList := make([]Item, r.domainLength[code]) - err = varbin.Read(r.bufferedReader, binary.BigEndian, &itemList) - if err != nil { - return nil, err + for i := range itemList { + typeByte, err := r.bufferedReader.ReadByte() + if err != nil { + return nil, err + } + itemList[i].Type = ItemType(typeByte) + itemList[i].Value, err = readString(r.bufferedReader) + if err != nil { + return nil, err + } } return itemList, nil } @@ -135,3 +141,18 @@ func (r *readCounter) Read(p []byte) (n int, err error) { } return } + +func readString(reader io.ByteReader) (string, error) { + length, err := binary.ReadUvarint(reader) + if err != nil { + return "", err + } + bytes := make([]byte, length) + for i := range bytes { + bytes[i], err = reader.ReadByte() + if err != nil { + return "", err + } + } + return string(bytes), nil +} diff --git a/common/geosite/writer.go b/common/geosite/writer.go index 1615fa34..52f2f7b9 100644 --- a/common/geosite/writer.go +++ b/common/geosite/writer.go @@ -2,7 +2,6 @@ package geosite import ( "bytes" - "encoding/binary" "sort" "github.com/sagernet/sing/common/varbin" @@ -20,7 +19,11 @@ func Write(writer varbin.Writer, domains map[string][]Item) error { for _, code := range keys { index[code] = content.Len() for _, item := range domains[code] { - err := varbin.Write(content, binary.BigEndian, item) + err := content.WriteByte(byte(item.Type)) + if err != nil { + return err + } + err = writeString(content, item.Value) if err != nil { return err } @@ -38,7 +41,7 @@ func Write(writer varbin.Writer, domains map[string][]Item) error { } for _, code := range keys { - err = varbin.Write(writer, binary.BigEndian, code) + err = writeString(writer, code) if err != nil { return err } @@ -59,3 +62,12 @@ func Write(writer varbin.Writer, domains map[string][]Item) error { return nil } + +func writeString(writer varbin.Writer, value string) error { + _, err := varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + _, err = writer.Write([]byte(value)) + return err +} diff --git a/common/srs/binary.go b/common/srs/binary.go index 0c93c284..ca12fff0 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "io" "net/netip" + "unsafe" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" @@ -505,7 +506,24 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen } func readRuleItemString(reader varbin.Reader) ([]string, error) { - return varbin.ReadValue[[]string](reader, binary.BigEndian) + length, err := binary.ReadUvarint(reader) + if err != nil { + return nil, err + } + result := make([]string, length) + for i := range result { + strLen, err := binary.ReadUvarint(reader) + if err != nil { + return nil, err + } + buf := make([]byte, strLen) + _, err = io.ReadFull(reader, buf) + if err != nil { + return nil, err + } + result[i] = string(buf) + } + return result, nil } func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error { @@ -513,11 +531,34 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e if err != nil { return err } - return varbin.Write(writer, binary.BigEndian, value) + _, err = varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + for _, s := range value { + _, err = varbin.WriteUvarint(writer, uint64(len(s))) + if err != nil { + return err + } + _, err = writer.Write([]byte(s)) + if err != nil { + return err + } + } + return nil } func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) { - return varbin.ReadValue[[]E](reader, binary.BigEndian) + length, err := binary.ReadUvarint(reader) + if err != nil { + return nil, err + } + result := make([]E, length) + _, err = io.ReadFull(reader, *(*[]byte)(unsafe.Pointer(&result))) + if err != nil { + return nil, err + } + return result, nil } func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error { @@ -525,11 +566,25 @@ func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value [] if err != nil { return err } - return varbin.Write(writer, binary.BigEndian, value) + _, err = varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + _, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value))) + return err } func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) { - return varbin.ReadValue[[]uint16](reader, binary.BigEndian) + length, err := binary.ReadUvarint(reader) + if err != nil { + return nil, err + } + result := make([]uint16, length) + err = binary.Read(reader, binary.BigEndian, result) + if err != nil { + return nil, err + } + return result, nil } func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error { @@ -537,7 +592,11 @@ func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) e if err != nil { return err } - return varbin.Write(writer, binary.BigEndian, value) + _, err = varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + return binary.Write(writer, binary.BigEndian, value) } func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error { diff --git a/common/srs/compat_test.go b/common/srs/compat_test.go new file mode 100644 index 00000000..98552b32 --- /dev/null +++ b/common/srs/compat_test.go @@ -0,0 +1,494 @@ +package srs + +import ( + "bufio" + "bytes" + "encoding/binary" + "net/netip" + "strings" + "testing" + "unsafe" + + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/varbin" + + "github.com/stretchr/testify/require" + "go4.org/netipx" +) + +// Old implementations using varbin reflection-based serialization + +func oldWriteStringSlice(writer varbin.Writer, value []string) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldReadStringSlice(reader varbin.Reader) ([]string, error) { + //nolint:staticcheck + return varbin.ReadValue[[]string](reader, binary.BigEndian) +} + +func oldWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldReadUint8Slice[E ~uint8](reader varbin.Reader) ([]E, error) { + //nolint:staticcheck + return varbin.ReadValue[[]E](reader, binary.BigEndian) +} + +func oldWriteUint16Slice(writer varbin.Writer, value []uint16) error { + //nolint:staticcheck + return varbin.Write(writer, binary.BigEndian, value) +} + +func oldReadUint16Slice(reader varbin.Reader) ([]uint16, error) { + //nolint:staticcheck + return varbin.ReadValue[[]uint16](reader, binary.BigEndian) +} + +func oldWritePrefix(writer varbin.Writer, prefix netip.Prefix) error { + //nolint:staticcheck + err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice()) + if err != nil { + return err + } + return binary.Write(writer, binary.BigEndian, uint8(prefix.Bits())) +} + +type oldIPRangeData struct { + From []byte + To []byte +} + +// Note: The old writeIPSet had a bug where varbin.Write(writer, binary.BigEndian, data) +// with a struct VALUE (not pointer) silently wrote nothing because field.CanSet() returned false. +// This caused IP range data to be missing from the output. +// The new implementation correctly writes all range data. +// +// The old readIPSet used varbin.Read with a pre-allocated slice, which worked because +// slice elements are addressable and CanSet() returns true for them. +// +// For compatibility testing, we verify: +// 1. New write produces correct output with range data +// 2. New read can parse the new format correctly +// 3. Round-trip works correctly + +func oldReadIPSet(reader varbin.Reader) (*netipx.IPSet, error) { + version, err := reader.ReadByte() + if err != nil { + return nil, err + } + if version != 1 { + return nil, err + } + var length uint64 + err = binary.Read(reader, binary.BigEndian, &length) + if err != nil { + return nil, err + } + ranges := make([]oldIPRangeData, length) + //nolint:staticcheck + err = varbin.Read(reader, binary.BigEndian, &ranges) + if err != nil { + return nil, err + } + mySet := &myIPSet{ + rr: make([]myIPRange, len(ranges)), + } + for i, rangeData := range ranges { + mySet.rr[i].from = M.AddrFromIP(rangeData.From) + mySet.rr[i].to = M.AddrFromIP(rangeData.To) + } + return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil +} + +// New write functions (without itemType prefix for testing) + +func newWriteStringSlice(writer varbin.Writer, value []string) error { + _, err := varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + for _, s := range value { + _, err = varbin.WriteUvarint(writer, uint64(len(s))) + if err != nil { + return err + } + _, err = writer.Write([]byte(s)) + if err != nil { + return err + } + } + return nil +} + +func newWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error { + _, err := varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + _, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value))) + return err +} + +func newWriteUint16Slice(writer varbin.Writer, value []uint16) error { + _, err := varbin.WriteUvarint(writer, uint64(len(value))) + if err != nil { + return err + } + return binary.Write(writer, binary.BigEndian, value) +} + +func newWritePrefix(writer varbin.Writer, prefix netip.Prefix) error { + addrSlice := prefix.Addr().AsSlice() + _, err := varbin.WriteUvarint(writer, uint64(len(addrSlice))) + if err != nil { + return err + } + _, err = writer.Write(addrSlice) + if err != nil { + return err + } + return writer.WriteByte(uint8(prefix.Bits())) +} + +// Tests + +func TestStringSliceCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input []string + }{ + {"nil", nil}, + {"empty", []string{}}, + {"single_empty", []string{""}}, + {"single", []string{"test"}}, + {"multi", []string{"a", "b", "c"}}, + {"with_empty", []string{"a", "", "c"}}, + {"utf8", []string{"测试", "テスト", "тест"}}, + {"long_string", []string{strings.Repeat("x", 128)}}, + {"many_elements", generateStrings(128)}, + {"many_elements_256", generateStrings(256)}, + {"127_byte_string", []string{strings.Repeat("x", 127)}}, + {"128_byte_string", []string{strings.Repeat("x", 128)}}, + {"mixed_lengths", []string{"a", strings.Repeat("b", 100), "", strings.Repeat("c", 200)}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteStringSlice(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWriteStringSlice(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadStringSlice(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireStringSliceEqual(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readRuleItemString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + requireStringSliceEqual(t, tc.input, readBack2) + }) + } +} + +func TestUint8SliceCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input []uint8 + }{ + {"nil", nil}, + {"empty", []uint8{}}, + {"single_zero", []uint8{0}}, + {"single_max", []uint8{255}}, + {"multi", []uint8{0, 1, 127, 128, 255}}, + {"boundary", []uint8{0x00, 0x7f, 0x80, 0xff}}, + {"sequential", generateUint8Slice(256)}, + {"127_elements", generateUint8Slice(127)}, + {"128_elements", generateUint8Slice(128)}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteUint8Slice(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWriteUint8Slice(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadUint8Slice[uint8](bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireUint8SliceEqual(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readRuleItemUint8[uint8](bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + requireUint8SliceEqual(t, tc.input, readBack2) + }) + } +} + +func TestUint16SliceCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input []uint16 + }{ + {"nil", nil}, + {"empty", []uint16{}}, + {"single_zero", []uint16{0}}, + {"single_max", []uint16{65535}}, + {"multi", []uint16{0, 255, 256, 32767, 32768, 65535}}, + {"ports", []uint16{80, 443, 8080, 8443}}, + {"127_elements", generateUint16Slice(127)}, + {"128_elements", generateUint16Slice(128)}, + {"256_elements", generateUint16Slice(256)}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWriteUint16Slice(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWriteUint16Slice(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> old read + readBack, err := oldReadUint16Slice(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireUint16SliceEqual(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readRuleItemUint16(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + requireUint16SliceEqual(t, tc.input, readBack2) + }) + } +} + +func TestPrefixCompat(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + input netip.Prefix + }{ + {"ipv4_0", netip.MustParsePrefix("0.0.0.0/0")}, + {"ipv4_8", netip.MustParsePrefix("10.0.0.0/8")}, + {"ipv4_16", netip.MustParsePrefix("192.168.0.0/16")}, + {"ipv4_24", netip.MustParsePrefix("192.168.1.0/24")}, + {"ipv4_32", netip.MustParsePrefix("1.2.3.4/32")}, + {"ipv6_0", netip.MustParsePrefix("::/0")}, + {"ipv6_64", netip.MustParsePrefix("2001:db8::/64")}, + {"ipv6_128", netip.MustParsePrefix("::1/128")}, + {"ipv6_full", netip.MustParsePrefix("2001:0db8:85a3:0000:0000:8a2e:0370:7334/128")}, + {"ipv4_private", netip.MustParsePrefix("172.16.0.0/12")}, + {"ipv6_link_local", netip.MustParsePrefix("fe80::/10")}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Old write + var oldBuf bytes.Buffer + err := oldWritePrefix(&oldBuf, tc.input) + require.NoError(t, err) + + // New write + var newBuf bytes.Buffer + err = newWritePrefix(&newBuf, tc.input) + require.NoError(t, err) + + // Bytes must match + require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(), + "mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes()) + + // New write -> new read (no old read for prefix) + readBack, err := readPrefix(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack) + + // Old write -> new read + readBack2, err := readPrefix(bufio.NewReader(bytes.NewReader(oldBuf.Bytes()))) + require.NoError(t, err) + require.Equal(t, tc.input, readBack2) + }) + } +} + +func TestIPSetCompat(t *testing.T) { + t.Parallel() + + // Note: The old writeIPSet was buggy (varbin.Write with struct values wrote nothing). + // This test verifies the new implementation writes correct data and round-trips correctly. + + cases := []struct { + name string + input *netipx.IPSet + }{ + {"single_ipv4", buildIPSet("1.2.3.4")}, + {"ipv4_range", buildIPSet("192.168.0.0/16")}, + {"multi_ipv4", buildIPSet("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")}, + {"single_ipv6", buildIPSet("::1")}, + {"ipv6_range", buildIPSet("2001:db8::/32")}, + {"mixed", buildIPSet("10.0.0.0/8", "::1", "2001:db8::/32")}, + {"large", buildLargeIPSet(100)}, + {"adjacent_ranges", buildIPSet("192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24")}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // New write + var newBuf bytes.Buffer + err := writeIPSet(&newBuf, tc.input) + require.NoError(t, err) + + // Verify format starts with version byte (1) + uint64 count + require.True(t, len(newBuf.Bytes()) >= 9, "output too short") + require.Equal(t, byte(1), newBuf.Bytes()[0], "version byte mismatch") + + // New write -> old read (varbin.Read with pre-allocated slice works correctly) + readBack, err := oldReadIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireIPSetEqual(t, tc.input, readBack) + + // New write -> new read + readBack2, err := readIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes()))) + require.NoError(t, err) + requireIPSetEqual(t, tc.input, readBack2) + }) + } +} + +// Helper functions + +func generateStrings(count int) []string { + result := make([]string, count) + for i := range result { + result[i] = strings.Repeat("x", i%50) + } + return result +} + +func generateUint8Slice(count int) []uint8 { + result := make([]uint8, count) + for i := range result { + result[i] = uint8(i % 256) + } + return result +} + +func generateUint16Slice(count int) []uint16 { + result := make([]uint16, count) + for i := range result { + result[i] = uint16(i * 257) + } + return result +} + +func buildIPSet(cidrs ...string) *netipx.IPSet { + var builder netipx.IPSetBuilder + for _, cidr := range cidrs { + prefix, err := netip.ParsePrefix(cidr) + if err != nil { + addr, err := netip.ParseAddr(cidr) + if err != nil { + panic(err) + } + builder.Add(addr) + } else { + builder.AddPrefix(prefix) + } + } + set, _ := builder.IPSet() + return set +} + +func buildLargeIPSet(count int) *netipx.IPSet { + var builder netipx.IPSetBuilder + for i := 0; i < count; i++ { + prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24) + builder.AddPrefix(prefix) + } + set, _ := builder.IPSet() + return set +} + +func requireStringSliceEqual(t *testing.T, expected, actual []string) { + t.Helper() + if len(expected) == 0 && len(actual) == 0 { + return + } + require.Equal(t, expected, actual) +} + +func requireUint8SliceEqual(t *testing.T, expected, actual []uint8) { + t.Helper() + if len(expected) == 0 && len(actual) == 0 { + return + } + require.Equal(t, expected, actual) +} + +func requireUint16SliceEqual(t *testing.T, expected, actual []uint16) { + t.Helper() + if len(expected) == 0 && len(actual) == 0 { + return + } + require.Equal(t, expected, actual) +} + +func requireIPSetEqual(t *testing.T, expected, actual *netipx.IPSet) { + t.Helper() + expectedRanges := expected.Ranges() + actualRanges := actual.Ranges() + require.Equal(t, len(expectedRanges), len(actualRanges), "range count mismatch") + for i := range expectedRanges { + require.Equal(t, expectedRanges[i].From(), actualRanges[i].From(), "range[%d].from mismatch", i) + require.Equal(t, expectedRanges[i].To(), actualRanges[i].To(), "range[%d].to mismatch", i) + } +} diff --git a/common/srs/ip_cidr.go b/common/srs/ip_cidr.go index 93ae84ad..7c81abda 100644 --- a/common/srs/ip_cidr.go +++ b/common/srs/ip_cidr.go @@ -2,6 +2,7 @@ package srs import ( "encoding/binary" + "io" "net/netip" M "github.com/sagernet/sing/common/metadata" @@ -9,11 +10,16 @@ import ( ) func readPrefix(reader varbin.Reader) (netip.Prefix, error) { - addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian) + addrLen, err := binary.ReadUvarint(reader) if err != nil { return netip.Prefix{}, err } - prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian) + addrSlice := make([]byte, addrLen) + _, err = io.ReadFull(reader, addrSlice) + if err != nil { + return netip.Prefix{}, err + } + prefixBits, err := reader.ReadByte() if err != nil { return netip.Prefix{}, err } @@ -21,11 +27,16 @@ func readPrefix(reader varbin.Reader) (netip.Prefix, error) { } func writePrefix(writer varbin.Writer, prefix netip.Prefix) error { - err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice()) + addrSlice := prefix.Addr().AsSlice() + _, err := varbin.WriteUvarint(writer, uint64(len(addrSlice))) if err != nil { return err } - err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits())) + _, err = writer.Write(addrSlice) + if err != nil { + return err + } + err = writer.WriteByte(uint8(prefix.Bits())) if err != nil { return err } diff --git a/common/srs/ip_set.go b/common/srs/ip_set.go index 044dc823..a10ac08c 100644 --- a/common/srs/ip_set.go +++ b/common/srs/ip_set.go @@ -2,11 +2,11 @@ package srs import ( "encoding/binary" + "io" "net/netip" "os" "unsafe" - "github.com/sagernet/sing/common" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/varbin" @@ -22,11 +22,6 @@ type myIPRange struct { to netip.Addr } -type myIPRangeData struct { - From []byte - To []byte -} - func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) { version, err := reader.ReadByte() if err != nil { @@ -41,17 +36,30 @@ func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) { if err != nil { return nil, err } - ranges := make([]myIPRangeData, length) - err = varbin.Read(reader, binary.BigEndian, &ranges) - if err != nil { - return nil, err - } mySet := &myIPSet{ - rr: make([]myIPRange, len(ranges)), + rr: make([]myIPRange, length), } - for i, rangeData := range ranges { - mySet.rr[i].from = M.AddrFromIP(rangeData.From) - mySet.rr[i].to = M.AddrFromIP(rangeData.To) + for i := range mySet.rr { + fromLen, err := binary.ReadUvarint(reader) + if err != nil { + return nil, err + } + fromBytes := make([]byte, fromLen) + _, err = io.ReadFull(reader, fromBytes) + if err != nil { + return nil, err + } + toLen, err := binary.ReadUvarint(reader) + if err != nil { + return nil, err + } + toBytes := make([]byte, toLen) + _, err = io.ReadFull(reader, toBytes) + if err != nil { + return nil, err + } + mySet.rr[i].from = M.AddrFromIP(fromBytes) + mySet.rr[i].to = M.AddrFromIP(toBytes) } return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil } @@ -61,18 +69,27 @@ func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error { if err != nil { return err } - dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData { - return myIPRangeData{ - From: rr.from.AsSlice(), - To: rr.to.AsSlice(), - } - }) - err = binary.Write(writer, binary.BigEndian, uint64(len(dataList))) + mySet := (*myIPSet)(unsafe.Pointer(set)) + err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr))) if err != nil { return err } - for _, data := range dataList { - err = varbin.Write(writer, binary.BigEndian, data) + for _, rr := range mySet.rr { + fromBytes := rr.from.AsSlice() + _, err = varbin.WriteUvarint(writer, uint64(len(fromBytes))) + if err != nil { + return err + } + _, err = writer.Write(fromBytes) + if err != nil { + return err + } + toBytes := rr.to.AsSlice() + _, err = varbin.WriteUvarint(writer, uint64(len(toBytes))) + if err != nil { + return err + } + _, err = writer.Write(toBytes) if err != nil { return err } diff --git a/experimental/libbox/profile_import.go b/experimental/libbox/profile_import.go index 17671e56..c337d015 100644 --- a/experimental/libbox/profile_import.go +++ b/experimental/libbox/profile_import.go @@ -5,6 +5,7 @@ import ( "bytes" "compress/gzip" "encoding/binary" + "io" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/varbin" @@ -35,7 +36,7 @@ type ErrorMessage struct { func (e *ErrorMessage) Encode() []byte { var buffer bytes.Buffer buffer.WriteByte(MessageTypeError) - varbin.Write(&buffer, binary.BigEndian, e.Message) + writeString(&buffer, e.Message) return buffer.Bytes() } @@ -49,7 +50,7 @@ func DecodeErrorMessage(data []byte) (*ErrorMessage, error) { return nil, E.New("invalid message") } var message ErrorMessage - message.Message, err = varbin.ReadValue[string](reader, binary.BigEndian) + message.Message, err = readString(reader) if err != nil { return nil, err } @@ -87,7 +88,7 @@ func (e *ProfileEncoder) Encode() []byte { binary.Write(&buffer, binary.BigEndian, uint16(len(e.profiles))) for _, preview := range e.profiles { binary.Write(&buffer, binary.BigEndian, preview.ProfileID) - varbin.Write(&buffer, binary.BigEndian, preview.Name) + writeString(&buffer, preview.Name) binary.Write(&buffer, binary.BigEndian, preview.Type) } return buffer.Bytes() @@ -117,7 +118,7 @@ func (d *ProfileDecoder) Decode(data []byte) error { if err != nil { return err } - profile.Name, err = varbin.ReadValue[string](reader, binary.BigEndian) + profile.Name, err = readString(reader) if err != nil { return err } @@ -178,11 +179,11 @@ func (c *ProfileContent) Encode() []byte { buffer.WriteByte(1) gWriter := gzip.NewWriter(buffer) writer := bufio.NewWriter(gWriter) - varbin.Write(writer, binary.BigEndian, c.Name) + writeStringBuffered(writer, c.Name) binary.Write(writer, binary.BigEndian, c.Type) - varbin.Write(writer, binary.BigEndian, c.Config) + writeStringBuffered(writer, c.Config) if c.Type != ProfileTypeLocal { - varbin.Write(writer, binary.BigEndian, c.RemotePath) + writeStringBuffered(writer, c.RemotePath) } if c.Type == ProfileTypeRemote { binary.Write(writer, binary.BigEndian, c.AutoUpdate) @@ -214,7 +215,7 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { } bReader := varbin.StubReader(gReader) var content ProfileContent - content.Name, err = varbin.ReadValue[string](bReader, binary.BigEndian) + content.Name, err = readString(bReader) if err != nil { return nil, err } @@ -222,12 +223,12 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { if err != nil { return nil, err } - content.Config, err = varbin.ReadValue[string](bReader, binary.BigEndian) + content.Config, err = readString(bReader) if err != nil { return nil, err } if content.Type != ProfileTypeLocal { - content.RemotePath, err = varbin.ReadValue[string](bReader, binary.BigEndian) + content.RemotePath, err = readString(bReader) if err != nil { return nil, err } @@ -250,3 +251,28 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) { } return &content, nil } + +func readString(reader io.ByteReader) (string, error) { + length, err := binary.ReadUvarint(reader) + if err != nil { + return "", err + } + buf := make([]byte, length) + for i := range buf { + buf[i], err = reader.ReadByte() + if err != nil { + return "", err + } + } + return string(buf), nil +} + +func writeString(buffer *bytes.Buffer, value string) { + varbin.WriteUvarint(buffer, uint64(len(value))) + buffer.WriteString(value) +} + +func writeStringBuffered(writer *bufio.Writer, value string) { + varbin.WriteUvarint(writer, uint64(len(value))) + writer.WriteString(value) +} diff --git a/go.mod b/go.mod index 91c529ec..80b4fa1c 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 - github.com/sagernet/sing v0.8.0-beta.11 + github.com/sagernet/sing v0.8.0-beta.12 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index b2c41002..b99db9b1 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.11 h1:nn/2Uod61b5rLHnXCuaFcbDxI1oRCodaxjzjt7Mobe4= -github.com/sagernet/sing v0.8.0-beta.11/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.12 h1:Xt0MNk6i6vI9f2vV6QkwhgiLB0g53fZSVsCCBEtQ8qQ= +github.com/sagernet/sing v0.8.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= From a4d5d5990152cfaaa7c56674e5045a73dd36a200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 28 Jan 2026 18:29:10 +0800 Subject: [PATCH 112/185] Minor fixes --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 80b4fa1c..c02c35fd 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.13 + github.com/sagernet/sing-tun v0.8.0-beta.14 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 diff --git a/go.sum b/go.sum index b99db9b1..1b066bd6 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.13 h1:vbI3uGthPIBU2lPOCbVK1YSLeV73ivV/olOUAvh1L2g= -github.com/sagernet/sing-tun v0.8.0-beta.13/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.14 h1:LlIhiynP1aggwDYeHDa+JeJAuptYy+OtQU+hPiHTXbY= +github.com/sagernet/sing-tun v0.8.0-beta.14/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From 79bab39502126638480028fdc85a9d7131560032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 29 Jan 2026 12:07:15 +0800 Subject: [PATCH 113/185] Fix auto_redirect fallback rule --- docs/changelog.md | 8 ++++ docs/configuration/inbound/tun.md | 15 ++++++- docs/configuration/inbound/tun.zh.md | 17 +++++++- go.mod | 2 +- go.sum | 4 +- option/tun.go | 61 ++++++++++++++------------- protocol/tun/inbound.go | 63 +++++++++++++++------------- 7 files changed, 105 insertions(+), 65 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index e95fc298..47e772c4 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,8 +4,16 @@ icon: material/alert-decagram #### 1.13.0-beta.7 +* Add fallback routing rule for `auto_redirect` **1** * Fixes and improvements +**1**: + +Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default), +ensuring traffic is routed to the sing-box table when no route is found in system tables. + +The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768). + #### 1.13.0-beta.6 * Update uTLS to v1.8.2 **1** diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 39393f42..7e67e488 100644 --- a/docs/configuration/inbound/tun.md +++ b/docs/configuration/inbound/tun.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) - :material-plus: [exclude_mptcp](#exclude_mptcp) + :material-plus: [exclude_mptcp](#exclude_mptcp) + :material-plus: [auto_redirect_iproute2_fallback_rule_index](#auto_redirect_iproute2_fallback_rule_index) !!! quote "Changes in sing-box 1.12.0" @@ -71,6 +72,7 @@ icon: material/new-box "auto_redirect_output_mark": "0x2024", "auto_redirect_reset_mark": "0x2025", "auto_redirect_nfqueue": 100, + "auto_redirect_iproute2_fallback_rule_index": 32768, "exclude_mptcp": false, "loopback_address": [ "10.7.0.1" @@ -303,6 +305,17 @@ NFQueue number used by `auto_redirect` pre-matching. `100` is used by default. +#### auto_redirect_iproute2_fallback_rule_index + +!!! question "Since sing-box 1.12.18" + +Linux iproute2 fallback rule index generated by `auto_redirect`. + +This rule is checked after system default rules (32766: main, 32767: default), +routing traffic to the sing-box table only when no route is found in system tables. + +`32768` is used by default. + #### exclude_mptcp !!! question "Since sing-box 1.13.0" diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index fa0c5d91..d8520aed 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/docs/configuration/inbound/tun.zh.md @@ -6,7 +6,8 @@ icon: material/new-box :material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark) :material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue) - :material-plus: [exclude_mptcp](#exclude_mptcp) + :material-plus: [exclude_mptcp](#exclude_mptcp) + :material-plus: [auto_redirect_iproute2_fallback_rule_index](#auto_redirect_iproute2_fallback_rule_index) !!! quote "sing-box 1.12.0 中的更改" @@ -71,6 +72,7 @@ icon: material/new-box "auto_redirect_output_mark": "0x2024", "auto_redirect_reset_mark": "0x2025", "auto_redirect_nfqueue": 100, + "auto_redirect_iproute2_fallback_rule_index": 32768, "exclude_mptcp": false, "loopback_address": [ "10.7.0.1" @@ -302,13 +304,24 @@ tun 接口的 IPv6 前缀。 默认使用 `100`。 +#### auto_redirect_iproute2_fallback_rule_index + +!!! question "自 sing-box 1.12.18 起" + +`auto_redirect` 生成的 iproute2 回退规则索引。 + +此规则在系统默认规则(32766: main,32767: default)之后检查, +仅当系统路由表中未找到路由时才将流量路由到 sing-box 路由表。 + +默认使用 `32768`。 + #### exclude_mptcp !!! question "自 sing-box 1.13.0 起" !!! quote "" - 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 + 仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。 由于协议限制,MPTCP 无法被透明代理。 diff --git a/go.mod b/go.mod index c02c35fd..62416534 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.14 + github.com/sagernet/sing-tun v0.8.0-beta.15 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 diff --git a/go.sum b/go.sum index 1b066bd6..e28e16bd 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.14 h1:LlIhiynP1aggwDYeHDa+JeJAuptYy+OtQU+hPiHTXbY= -github.com/sagernet/sing-tun v0.8.0-beta.14/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.15 h1:R5PMHCXuSUs5g6aor4UhxfaIx+t6yofAYB9YAea7UZM= +github.com/sagernet/sing-tun v0.8.0-beta.15/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= diff --git a/option/tun.go b/option/tun.go index 48989c9f..72b6e456 100644 --- a/option/tun.go +++ b/option/tun.go @@ -11,36 +11,37 @@ import ( ) type TunInboundOptions struct { - InterfaceName string `json:"interface_name,omitempty"` - MTU uint32 `json:"mtu,omitempty"` - Address badoption.Listable[netip.Prefix] `json:"address,omitempty"` - AutoRoute bool `json:"auto_route,omitempty"` - IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` - IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"` - AutoRedirect bool `json:"auto_redirect,omitempty"` - AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` - AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` - AutoRedirectResetMark FwMark `json:"auto_redirect_reset_mark,omitempty"` - AutoRedirectNFQueue uint16 `json:"auto_redirect_nfqueue,omitempty"` - ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"` - LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"` - StrictRoute bool `json:"strict_route,omitempty"` - RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"` - RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"` - RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` - RouteExcludeAddressSet badoption.Listable[string] `json:"route_exclude_address_set,omitempty"` - IncludeInterface badoption.Listable[string] `json:"include_interface,omitempty"` - ExcludeInterface badoption.Listable[string] `json:"exclude_interface,omitempty"` - IncludeUID badoption.Listable[uint32] `json:"include_uid,omitempty"` - IncludeUIDRange badoption.Listable[string] `json:"include_uid_range,omitempty"` - ExcludeUID badoption.Listable[uint32] `json:"exclude_uid,omitempty"` - ExcludeUIDRange badoption.Listable[string] `json:"exclude_uid_range,omitempty"` - IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"` - IncludePackage badoption.Listable[string] `json:"include_package,omitempty"` - ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - Stack string `json:"stack,omitempty"` - Platform *TunPlatformOptions `json:"platform,omitempty"` + InterfaceName string `json:"interface_name,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + Address badoption.Listable[netip.Prefix] `json:"address,omitempty"` + AutoRoute bool `json:"auto_route,omitempty"` + IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` + IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"` + AutoRedirect bool `json:"auto_redirect,omitempty"` + AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` + AutoRedirectResetMark FwMark `json:"auto_redirect_reset_mark,omitempty"` + AutoRedirectNFQueue uint16 `json:"auto_redirect_nfqueue,omitempty"` + AutoRedirectFallbackRuleIndex int `json:"auto_redirect_iproute2_fallback_rule_index,omitempty"` + ExcludeMPTCP bool `json:"exclude_mptcp,omitempty"` + LoopbackAddress badoption.Listable[netip.Addr] `json:"loopback_address,omitempty"` + StrictRoute bool `json:"strict_route,omitempty"` + RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"` + RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"` + RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` + RouteExcludeAddressSet badoption.Listable[string] `json:"route_exclude_address_set,omitempty"` + IncludeInterface badoption.Listable[string] `json:"include_interface,omitempty"` + ExcludeInterface badoption.Listable[string] `json:"exclude_interface,omitempty"` + IncludeUID badoption.Listable[uint32] `json:"include_uid,omitempty"` + IncludeUIDRange badoption.Listable[string] `json:"include_uid_range,omitempty"` + ExcludeUID badoption.Listable[uint32] `json:"exclude_uid,omitempty"` + ExcludeUIDRange badoption.Listable[string] `json:"exclude_uid_range,omitempty"` + IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"` + IncludePackage badoption.Listable[string] `json:"include_package,omitempty"` + ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Stack string `json:"stack,omitempty"` + Platform *TunPlatformOptions `json:"platform,omitempty"` InboundOptions // Deprecated: removed diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 2ab68b3e..70f82044 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -174,6 +174,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo if ruleIndex == 0 { ruleIndex = tun.DefaultIPRoute2RuleIndex } + autoRedirectFallbackRuleIndex := options.AutoRedirectFallbackRuleIndex + if autoRedirectFallbackRuleIndex == 0 { + autoRedirectFallbackRuleIndex = tun.DefaultIPRoute2AutoRedirectFallbackRuleIndex + } inputMark := uint32(options.AutoRedirectInputMark) if inputMark == 0 { inputMark = tun.DefaultAutoRedirectInputMark @@ -200,35 +204,36 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo logger: logger, inboundOptions: options.InboundOptions, tunOptions: tun.Options{ - Name: options.InterfaceName, - MTU: tunMTU, - GSO: enableGSO, - Inet4Address: inet4Address, - Inet6Address: inet6Address, - AutoRoute: options.AutoRoute, - IPRoute2TableIndex: tableIndex, - IPRoute2RuleIndex: ruleIndex, - AutoRedirectInputMark: inputMark, - AutoRedirectOutputMark: outputMark, - AutoRedirectResetMark: resetMark, - AutoRedirectNFQueue: nfQueue, - ExcludeMPTCP: options.ExcludeMPTCP, - Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4), - Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6), - StrictRoute: options.StrictRoute, - IncludeInterface: options.IncludeInterface, - ExcludeInterface: options.ExcludeInterface, - Inet4RouteAddress: inet4RouteAddress, - Inet6RouteAddress: inet6RouteAddress, - Inet4RouteExcludeAddress: inet4RouteExcludeAddress, - Inet6RouteExcludeAddress: inet6RouteExcludeAddress, - IncludeUID: includeUID, - ExcludeUID: excludeUID, - IncludeAndroidUser: options.IncludeAndroidUser, - IncludePackage: options.IncludePackage, - ExcludePackage: options.ExcludePackage, - InterfaceMonitor: networkManager.InterfaceMonitor(), - EXP_MultiPendingPackets: multiPendingPackets, + Name: options.InterfaceName, + MTU: tunMTU, + GSO: enableGSO, + Inet4Address: inet4Address, + Inet6Address: inet6Address, + AutoRoute: options.AutoRoute, + IPRoute2TableIndex: tableIndex, + IPRoute2RuleIndex: ruleIndex, + IPRoute2AutoRedirectFallbackRuleIndex: autoRedirectFallbackRuleIndex, + AutoRedirectInputMark: inputMark, + AutoRedirectOutputMark: outputMark, + AutoRedirectResetMark: resetMark, + AutoRedirectNFQueue: nfQueue, + ExcludeMPTCP: options.ExcludeMPTCP, + Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4), + Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6), + StrictRoute: options.StrictRoute, + IncludeInterface: options.IncludeInterface, + ExcludeInterface: options.ExcludeInterface, + Inet4RouteAddress: inet4RouteAddress, + Inet6RouteAddress: inet6RouteAddress, + Inet4RouteExcludeAddress: inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: inet6RouteExcludeAddress, + IncludeUID: includeUID, + ExcludeUID: excludeUID, + IncludeAndroidUser: options.IncludeAndroidUser, + IncludePackage: options.IncludePackage, + ExcludePackage: options.ExcludePackage, + InterfaceMonitor: networkManager.InterfaceMonitor(), + EXP_MultiPendingPackets: multiPendingPackets, }, udpTimeout: udpTimeout, stack: options.Stack, From ff58edb1c13e0a40792e282b9ecd9a24a4ba415a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 28 Jan 2026 18:27:00 +0800 Subject: [PATCH 114/185] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/clients/android b/clients/android index 39e961fc..d0366839 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 39e961fcbc7f079d04297d7871932514cb8b6003 +Subproject commit d036683923f41871196125a640bc1211b6878d82 diff --git a/clients/apple b/clients/apple index 97402ba8..9d4ce37e 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 97402ba8b64d9bcc49d7d1dd73aca7ca5b46da54 +Subproject commit 9d4ce37e94e3df16b71ba3156ee45faf96241478 diff --git a/docs/changelog.md b/docs/changelog.md index 47e772c4..54e5f7e8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,19 @@ icon: material/alert-decagram --- -#### 1.13.0-beta.7 +#### 1.13.0-beta.8 + +* Add fallback routing rule for `auto_redirect` **1** +* Fixes and improvements + +**1**: + +Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default), +ensuring traffic is routed to the sing-box table when no route is found in system tables. + +The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768). + +#### 1.12.18 * Add fallback routing rule for `auto_redirect` **1** * Fixes and improvements From 8dd8897fd8739f2d37785b8e51f64b2c2c51149f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 1 Feb 2026 10:48:05 +0800 Subject: [PATCH 115/185] Fix varbin serialization --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 62416534..3db62e38 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 - github.com/sagernet/sing v0.8.0-beta.12 + github.com/sagernet/sing v0.8.0-beta.14 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index e28e16bd..77290874 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.12 h1:Xt0MNk6i6vI9f2vV6QkwhgiLB0g53fZSVsCCBEtQ8qQ= -github.com/sagernet/sing v0.8.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.14 h1:6+XYm3izyyCdplUn3hdjWnP5PMKzgD6V8ppJnGR8znU= +github.com/sagernet/sing v0.8.0-beta.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= From 432fe1b3c9038600a24f0e6c67e7ec41ae396e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 1 Feb 2026 10:49:12 +0800 Subject: [PATCH 116/185] Disable rp filter atomically --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3db62e38..08752b98 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.15 + github.com/sagernet/sing-tun v0.8.0-beta.16 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 diff --git a/go.sum b/go.sum index 77290874..482cf7bc 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.15 h1:R5PMHCXuSUs5g6aor4UhxfaIx+t6yofAYB9YAea7UZM= -github.com/sagernet/sing-tun v0.8.0-beta.15/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.16 h1:Fva6gwzRL3FsOU2GNg3zyunX+ja6OkiujbLvjLDl1a4= +github.com/sagernet/sing-tun v0.8.0-beta.16/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From c1dc6cb0fb428edf152c8e6cc600924b0fd6fd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 1 Feb 2026 10:49:15 +0800 Subject: [PATCH 117/185] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 134 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/clients/android b/clients/android index d0366839..223b5899 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit d036683923f41871196125a640bc1211b6878d82 +Subproject commit 223b5899c5be6a5035ad5bce771901babf02b1a0 diff --git a/clients/apple b/clients/apple index 9d4ce37e..38e8b3ed 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 9d4ce37e94e3df16b71ba3156ee45faf96241478 +Subproject commit 38e8b3eda9f0203dbe63c543f9c5f731ce7961c5 diff --git a/docs/changelog.md b/docs/changelog.md index 54e5f7e8..b03a9d21 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,140 @@ icon: material/alert-decagram --- +#### 1.13.0-rc.1 + +* Fixes and improvements + +Important changes since 1.12: + +* Add NaiveProxy outbound **1** +* Add pre-match support for `auto_redirect` **2** +* Improve `auto_redirect` **3** +* Add Chrome Root Store certificate option **4** +* Add new options for ACME DNS-01 challenge providers **5** +* Add Wi-Fi state support for Linux and Windows **6** +* Add curve preferences, pinned public key SHA256, mTLS and ECH `query_server_name` for TLS options **7** +* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for dial fields **8** +* Add `bind_address_no_port` option for dial fields **9** +* Add system interface support for Tailscale endpoint **10** +* Add Claude Code Multiplexer service **11** +* Add OpenAI Codex Multiplexer service **12** +* Apple/Android: Refactor GUI +* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs) +* Android: Add support for resisting VPN detection via Xposed +* Drop support for go1.23 **13** +* Drop support for Android 5.0 **14** +* Update uTLS to v1.8.2 **15** +* Update quic-go to v0.59.0 +* Update gVisor to v20250811 +* Update Tailscale to v1.92.4 + +**1**: + +NaiveProxy outbound now supports QUIC, ECH, UDP over TCP, and configurable QUIC congestion control. + +Only available on Apple platforms, Android, Windows and some Linux architectures. +Each Windows release includes `libcronet.dll` — +ensure this file is in the same directory as `sing-box.exe` or in a directory listed in `PATH`. + +See [NaiveProxy outbound](/configuration/outbound/naive/). + +**2**: + +`auto_redirect` now allows you to bypass sing-box for connections based on routing rules. + +A new rule action `bypass` is introduced to support this feature. When matched during pre-match, the connection will bypass sing-box and connect directly. + +This feature requires Linux with `auto_redirect` enabled. + +See [Pre-match](/configuration/shared/pre-match/) and [Rule Action](/configuration/route/rule_action/#bypass). + +**3**: + +`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues. +You can change it to bypass sing-box via the new `exclude_mptcp` option. + +Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default), +ensuring traffic is routed to the sing-box table when no route is found in system tables. +The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768). + +See [TUN](/configuration/inbound/tun/#exclude_mptcp). + +**4**: + +Adds `chrome` as a new certificate store option alongside `mozilla`. +Both stores filter out China-based CA certificates. + +See [Certificate](/configuration/certificate/#store). + +**5**: + +See [DNS-01 Challenge](/configuration/shared/dns01_challenge/). + +**6**: + +sing-box can now monitor Wi-Fi state on Linux and Windows to enable routing rules based on `wifi_ssid` and `wifi_bssid`. + +See [Wi-Fi State](/configuration/shared/wifi-state/). + +**7**: + +See [TLS](/configuration/shared/tls/). + +**8**: + +The default TCP keep-alive initial period has been updated from 10 minutes to 5 minutes. + +See [Dial Fields](/configuration/shared/dial/#tcp_keep_alive). + +**9**: + +Adds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address. + +This allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios. + +See [Dial Fields](/configuration/shared/dial/#bind_address_no_port). + +**10**: + +Tailscale endpoint can now create a system TUN interface to handle traffic directly. + +See [Tailscale endpoint](/configuration/endpoint/tailscale/#system_interface). + +**11**: + +CCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients. + +See [CCM](/configuration/service/ccm). + +**12**: + +See [OCM](/configuration/service/ocm). + +**13**: + +Due to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile. + +**14**: + +Due to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0, +and only through a separate legacy build (with `-legacy-android-5` suffix). + +For standalone binaries, the minimum Android version has been raised to Android 6.0, +since Termux requires Android 7.0 or later. + +**15**: + +This update fixes missing padding extension for Chrome 120+ fingerprints. + +Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities. +uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; +use NaiveProxy instead for TLS fingerprint resistance. + +#### 1.12.19 + +* Fixes and improvements + #### 1.13.0-beta.8 * Add fallback routing rule for `auto_redirect` **1** From a05e05a47ccb2589d561a21eac74241879045fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 2 Feb 2026 14:15:55 +0800 Subject: [PATCH 118/185] Fix random iproute2 table index was incorrectly removed --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 08752b98..ab691aaa 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.16 + github.com/sagernet/sing-tun v0.8.0-beta.17 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 diff --git a/go.sum b/go.sum index 482cf7bc..89fc1344 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.16 h1:Fva6gwzRL3FsOU2GNg3zyunX+ja6OkiujbLvjLDl1a4= -github.com/sagernet/sing-tun v0.8.0-beta.16/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.17 h1:6DdbNXeTFYj8Tb4FCh8Mp2boA3rVY6VNqzTOObj7Xis= +github.com/sagernet/sing-tun v0.8.0-beta.17/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From 55b6e7dbfef9777ad750069b71ba95b9edf1e214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 2 Feb 2026 18:19:14 +0800 Subject: [PATCH 119/185] socks: Fix missing UDP timeout --- go.mod | 2 +- go.sum | 4 ++-- protocol/mixed/inbound.go | 11 ++++++++++- protocol/socks/inbound.go | 11 ++++++++++- protocol/tor/proxy.go | 2 +- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ab691aaa..5ebde511 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 - github.com/sagernet/sing v0.8.0-beta.14 + github.com/sagernet/sing v0.8.0-beta.15 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 89fc1344..bddfd235 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.14 h1:6+XYm3izyyCdplUn3hdjWnP5PMKzgD6V8ppJnGR8znU= -github.com/sagernet/sing v0.8.0-beta.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.15 h1:lP6XnzeQvVBfuTkByo5YnG4Oy/AVkDC2ZljghSfHzKQ= +github.com/sagernet/sing v0.8.0-beta.15/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index 5591c315..64c3edb5 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -4,6 +4,7 @@ import ( std_bufio "bufio" "context" "net" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/inbound" @@ -36,14 +37,22 @@ type Inbound struct { listener *listener.Listener authenticator *auth.Authenticator tlsConfig tls.ServerConfig + udpTimeout time.Duration } func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } inbound := &Inbound{ Adapter: inbound.NewAdapter(C.TypeMixed, tag), router: uot.NewRouter(router, logger), logger: logger, authenticator: auth.NewAuthenticator(options.Users), + udpTimeout: udpTimeout, } if options.TLS != nil { tlsConfig, err := tls.NewServerWithOptions(tls.ServerOptions{ @@ -116,7 +125,7 @@ func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata ada } switch headerBytes[0] { case socks4.Version, socks5.Version: - return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, metadata.Source, onClose) + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, h.udpTimeout, metadata.Source, onClose) default: return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) } diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go index 6b828152..68e0ef58 100644 --- a/protocol/socks/inbound.go +++ b/protocol/socks/inbound.go @@ -4,6 +4,7 @@ import ( std_bufio "bufio" "context" "net" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/inbound" @@ -31,14 +32,22 @@ type Inbound struct { logger logger.ContextLogger listener *listener.Listener authenticator *auth.Authenticator + udpTimeout time.Duration } func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) (adapter.Inbound, error) { + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } inbound := &Inbound{ Adapter: inbound.NewAdapter(C.TypeSOCKS, tag), router: uot.NewRouter(router, logger), logger: logger, authenticator: auth.NewAuthenticator(options.Users), + udpTimeout: udpTimeout, } inbound.listener = listener.New(listener.Options{ Context: ctx, @@ -62,7 +71,7 @@ func (h *Inbound) Close() error { } func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, metadata.Source, onClose) + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), h.listener, h.udpTimeout, metadata.Source, onClose) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { if E.IsClosedOrCanceled(err) { diff --git a/protocol/tor/proxy.go b/protocol/tor/proxy.go index 6b7db7c3..378e74fc 100644 --- a/protocol/tor/proxy.go +++ b/protocol/tor/proxy.go @@ -99,7 +99,7 @@ func (l *ProxyListener) acceptLoop() { } func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error { - return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, nil, M.SocksaddrFromNet(conn.RemoteAddr()), nil) + return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, nil, 0, M.SocksaddrFromNet(conn.RemoteAddr()), nil) } func (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { From baa9f29f0dfa47748c80101ee9d0b1edb8433214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 2 Feb 2026 22:16:08 +0800 Subject: [PATCH 120/185] documentation: Update release changelog --- docs/changelog.md | 71 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index b03a9d21..0e511c16 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.13.0-rc.1 +#### 1.13.0-rc.2 * Fixes and improvements @@ -15,17 +15,22 @@ Important changes since 1.12: * Add new options for ACME DNS-01 challenge providers **5** * Add Wi-Fi state support for Linux and Windows **6** * Add curve preferences, pinned public key SHA256, mTLS and ECH `query_server_name` for TLS options **7** -* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for dial fields **8** -* Add `bind_address_no_port` option for dial fields **9** -* Add system interface support for Tailscale endpoint **10** -* Add Claude Code Multiplexer service **11** -* Add OpenAI Codex Multiplexer service **12** +* Add kTLS support **8** +* Add ICMP echo (ping) proxy support **9** +* Add `interface_address`, `network_interface_address` and `default_interface_address` rule items **10** +* Add `preferred_by` route rule item **11** +* Improve `local` DNS server **12** +* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for listen and dial fields **13** +* Add `bind_address_no_port` option for dial fields **14** +* Add system interface and relay server options for Tailscale endpoint **15** +* Add Claude Code Multiplexer service **16** +* Add OpenAI Codex Multiplexer service **17** * Apple/Android: Refactor GUI * Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs) * Android: Add support for resisting VPN detection via Xposed -* Drop support for go1.23 **13** -* Drop support for Android 5.0 **14** -* Update uTLS to v1.8.2 **15** +* Drop support for go1.23 **18** +* Drop support for Android 5.0 **19** +* Update uTLS to v1.8.2 **20** * Update quic-go to v0.59.0 * Update gVisor to v20250811 * Update Tailscale to v1.92.4 @@ -84,11 +89,42 @@ See [TLS](/configuration/shared/tls/). **8**: +Adds `kernel_tx` and `kernel_rx` options for TLS inbound. +Enables kernel-level TLS offloading via `splice(2)` on Linux 5.1+ with TLS 1.3. + +See [TLS](/configuration/shared/tls/). + +**9**: + +sing-box can now proxy ICMP echo (ping) requests. +A new `icmp` network type is available for route rules. +Supported from TUN, WireGuard and Tailscale inbounds to Direct, WireGuard and Tailscale outbounds. +The `reject` action can also reply to ICMP echo requests. + +**10**: + +New rule items for matching based on interface IP addresses, available in route rules, DNS rules and rule-sets. + +**11**: + +Matches outbounds' preferred routes. +For Tailscale: MagicDNS domains and peers' allowed IPs. For WireGuard: peers' allowed IPs. + +**12**: + +The `local` DNS server now uses platform-native resolution: +`getaddrinfo`/libresolv on Apple platforms, systemd-resolved DBus on Linux. +A new `prefer_go` option is available to opt out. + +See [Local DNS](/configuration/dns/server/local/). + +**13**: + The default TCP keep-alive initial period has been updated from 10 minutes to 5 minutes. See [Dial Fields](/configuration/shared/dial/#tcp_keep_alive). -**9**: +**14**: Adds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address. @@ -96,27 +132,28 @@ This allows reusing the same source port for multiple connections, improving sca See [Dial Fields](/configuration/shared/dial/#bind_address_no_port). -**10**: +**15**: Tailscale endpoint can now create a system TUN interface to handle traffic directly. +New `relay_server_port` and `relay_server_static_endpoints` options for incoming relay connections. -See [Tailscale endpoint](/configuration/endpoint/tailscale/#system_interface). +See [Tailscale endpoint](/configuration/endpoint/tailscale/). -**11**: +**16**: CCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients. See [CCM](/configuration/service/ccm). -**12**: +**17**: See [OCM](/configuration/service/ocm). -**13**: +**18**: Due to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile. -**14**: +**19**: Due to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0, and only through a separate legacy build (with `-legacy-android-5` suffix). @@ -124,7 +161,7 @@ and only through a separate legacy build (with `-legacy-android-5` suffix). For standalone binaries, the minimum Android version has been raised to Android 6.0, since Termux requires Android 7.0 or later. -**15**: +**20**: This update fixes missing padding extension for Chrome 120+ fingerprints. From e11dbf3a8ee6b25c1219ee53610c534bcff451d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Feb 2026 02:51:05 +0800 Subject: [PATCH 121/185] bufio: Refactor copy --- go.mod | 2 +- go.sum | 4 +- route/conn.go | 151 ++++++++++++++++---------------------------------- 3 files changed, 51 insertions(+), 106 deletions(-) diff --git a/go.mod b/go.mod index 5ebde511..d7697105 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 - github.com/sagernet/sing v0.8.0-beta.15 + github.com/sagernet/sing v0.8.0-beta.15.0.20260202162209-7c477e13f41e github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index bddfd235..375d6038 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.15 h1:lP6XnzeQvVBfuTkByo5YnG4Oy/AVkDC2ZljghSfHzKQ= -github.com/sagernet/sing v0.8.0-beta.15/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.15.0.20260202162209-7c477e13f41e h1:H8izpW6d9l8Ub5UFSV/Q2WCehss2KAlmnDiABa4BHp0= +github.com/sagernet/sing v0.8.0-beta.15.0.20260202162209-7c477e13f41e/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= diff --git a/route/conn.go b/route/conn.go index 16654704..3e9c831c 100644 --- a/route/conn.go +++ b/route/conn.go @@ -2,7 +2,6 @@ package route import ( "context" - "errors" "io" "net" "net/netip" @@ -102,8 +101,12 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co m.connections.Remove(element) }) var done atomic.Bool - m.preConnectionCopy(ctx, conn, remoteConn, false, &done, onClose) - m.preConnectionCopy(ctx, remoteConn, conn, true, &done, onClose) + if m.kickWriteHandshake(ctx, conn, remoteConn, false, &done, onClose) { + return + } + if m.kickWriteHandshake(ctx, remoteConn, conn, true, &done, onClose) { + return + } go m.connectionCopy(ctx, conn, remoteConn, false, &done, onClose) go m.connectionCopy(ctx, remoteConn, conn, true, &done, onClose) } @@ -226,75 +229,8 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) } -func (m *ConnectionManager) preConnectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { - readHandshake := N.NeedHandshakeForRead(source) - writeHandshake := N.NeedHandshakeForWrite(destination) - if readHandshake || writeHandshake { - var err error - for { - err = m.connectionCopyEarlyWrite(source, destination, readHandshake, writeHandshake) - if err == nil && N.NeedHandshakeForRead(source) { - continue - } else if E.IsMulti(err, os.ErrInvalid, context.DeadlineExceeded, io.EOF) { - err = nil - } - break - } - if err != nil { - if done.Swap(true) { - onClose(err) - } - common.Close(source, destination) - if !direction { - m.logger.ErrorContext(ctx, "connection upload handshake: ", err) - } else { - m.logger.ErrorContext(ctx, "connection download handshake: ", err) - } - return - } - } -} - func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { - var ( - sourceReader io.Reader = source - destinationWriter io.Writer = destination - ) - var readCounters, writeCounters []N.CountFunc - for { - sourceReader, readCounters = N.UnwrapCountReader(sourceReader, readCounters) - destinationWriter, writeCounters = N.UnwrapCountWriter(destinationWriter, writeCounters) - if cachedSrc, isCached := sourceReader.(N.CachedReader); isCached { - cachedBuffer := cachedSrc.ReadCached() - if cachedBuffer != nil { - dataLen := cachedBuffer.Len() - _, err := destination.Write(cachedBuffer.Bytes()) - cachedBuffer.Release() - if err != nil { - if done.Swap(true) { - onClose(err) - } - common.Close(source, destination) - if !direction { - m.logger.ErrorContext(ctx, "connection upload payload: ", err) - } else { - m.logger.ErrorContext(ctx, "connection download payload: ", err) - } - return - } - for _, counter := range readCounters { - counter(int64(dataLen)) - } - for _, counter := range writeCounters { - counter(int64(dataLen)) - } - } - continue - } - break - } - - _, err := bufio.CopyWithCounters(destinationWriter, sourceReader, source, readCounters, writeCounters, bufio.DefaultIncreaseBufferAfter, bufio.DefaultBatchSize) + _, err := bufio.CopyWithIncreateBuffer(destination, source, bufio.DefaultIncreaseBufferAfter, bufio.DefaultBatchSize) if err != nil { common.Close(source, destination) } else if duplexDst, isDuplex := destination.(N.WriteCloser); isDuplex { @@ -328,45 +264,54 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, } } -func (m *ConnectionManager) connectionCopyEarlyWrite(source net.Conn, destination io.Writer, readHandshake bool, writeHandshake bool) error { - payload := buf.NewPacket() - defer payload.Release() - err := source.SetReadDeadline(time.Now().Add(C.ReadPayloadTimeout)) - if err != nil { - if err == os.ErrInvalid { - if writeHandshake { - return common.Error(destination.Write(nil)) - } - } - return err +func (m *ConnectionManager) kickWriteHandshake(ctx context.Context, source net.Conn, destination net.Conn, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) bool { + if !N.NeedHandshakeForWrite(destination) { + return false } var ( - isTimeout bool - isEOF bool + cachedBuffer *buf.Buffer + wrotePayload bool ) - _, err = payload.ReadOnceFrom(source) - if err != nil { - if E.IsTimeout(err) { - isTimeout = true - } else if errors.Is(err, io.EOF) { - isEOF = true - } else { - return E.Cause(err, "read payload") + sourceReader, readCounters := N.UnwrapCountReader(source, nil) + destinationWriter, writeCounters := N.UnwrapCountWriter(destination, nil) + if cachedReader, ok := sourceReader.(N.CachedReader); ok { + cachedBuffer = cachedReader.ReadCached() + } + var err error + if cachedBuffer != nil { + wrotePayload = true + dataLen := cachedBuffer.Len() + _, err = destinationWriter.Write(cachedBuffer.Bytes()) + cachedBuffer.Release() + if err == nil { + for _, counter := range readCounters { + counter(int64(dataLen)) + } + for _, counter := range writeCounters { + counter(int64(dataLen)) + } } + } else { + _ = destination.SetWriteDeadline(time.Now().Add(C.ReadPayloadTimeout)) + _, err = destinationWriter.Write(nil) + _ = destination.SetWriteDeadline(time.Time{}) } - _ = source.SetReadDeadline(time.Time{}) - if !payload.IsEmpty() || writeHandshake { - _, err = destination.Write(payload.Bytes()) - if err != nil { - return E.Cause(err, "write payload") - } + if err == nil { + return false } - if isTimeout { - return context.DeadlineExceeded - } else if isEOF { - return io.EOF + if !wrotePayload && (E.IsMulti(err, os.ErrInvalid, context.DeadlineExceeded, io.EOF) || E.IsTimeout(err)) { + return false } - return nil + if !done.Swap(true) { + onClose(err) + } + common.Close(source, destination) + if !direction { + m.logger.ErrorContext(ctx, "connection upload handshake: ", err) + } else { + m.logger.ErrorContext(ctx, "connection download handshake: ", err) + } + return true } func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.PacketReader, destination N.PacketWriter, direction bool, done *atomic.Bool, onClose N.CloseHandlerFunc) { From d230dae0a57f4224f3ee9e65c231b7e83c3a675b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 5 Feb 2026 17:23:49 +0800 Subject: [PATCH 122/185] Fix vmess crash --- protocol/vmess/outbound.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index cb5c674f..f0b41ae0 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -57,7 +57,9 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig) + if outbound.tlsConfig != nil { + outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig) + } } if options.Transport != nil { outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) From 15722b06ddce12f2dff7d8abffc5c35ae6c4b8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 5 Feb 2026 17:12:15 +0800 Subject: [PATCH 123/185] Update Go to 1.25.7 --- .github/setup_go_for_windows7.sh | 2 +- .github/workflows/build.yml | 10 +++++----- .github/workflows/linux.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/setup_go_for_windows7.sh b/.github/setup_go_for_windows7.sh index de440afa..fe31b944 100755 --- a/.github/setup_go_for_windows7.sh +++ b/.github/setup_go_for_windows7.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -VERSION="1.25.6" +VERSION="1.25.7" mkdir -p $HOME/go cd $HOME/go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5e41584..42122d03 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.6 + go-version: ^1.25.7 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -115,7 +115,7 @@ jobs: if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }} uses: actions/setup-go@v5 with: - go-version: ^1.25.6 + go-version: ^1.25.7 - name: Setup Go 1.24 if: matrix.legacy_go124 uses: actions/setup-go@v5 @@ -571,7 +571,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.6 + go-version: ^1.25.7 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -661,7 +661,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.6 + go-version: ^1.25.7 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -760,7 +760,7 @@ jobs: if: matrix.if uses: actions/setup-go@v5 with: - go-version: ^1.25.6 + go-version: ^1.25.7 - name: Set tag if: matrix.if run: |- diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f74a6c71..1311788e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.6 + go-version: ^1.25.7 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -77,7 +77,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.6 + go-version: ^1.25.7 - name: Clone cronet-go if: matrix.naive run: | From a2d313c59b40fd2936b32c069d98ba3b9f8ac09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 5 Feb 2026 17:23:54 +0800 Subject: [PATCH 124/185] Bump version --- clients/android | 2 +- docs/changelog.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/clients/android b/clients/android index 223b5899..02f9ec4d 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 223b5899c5be6a5035ad5bce771901babf02b1a0 +Subproject commit 02f9ec4d9723cc7f6be7af570baf82eb261d8656 diff --git a/docs/changelog.md b/docs/changelog.md index 0e511c16..d42d6b03 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -169,6 +169,14 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; use NaiveProxy instead for TLS fingerprint resistance. +#### 1.12.20 + +* Fixes and improvements + +#### 1.13.0-rc.1 + +* Fixes and improvements + #### 1.12.19 * Fixes and improvements From c45ea8dfac104c55e38e15dd68667217e9b14297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Feb 2026 19:35:32 +0800 Subject: [PATCH 125/185] Recover from bbolt panics on corrupted database When bbolt encounters corrupted page data at runtime, it panics instead of returning an error. Wrap all DB transactions with recover to catch these panics, delete the corrupted database file, and reopen a fresh one. --- experimental/cachefile/cache.go | 59 +++++++++++++++++++++++++++----- experimental/cachefile/fakeip.go | 12 +++---- experimental/cachefile/rdrc.go | 6 ++-- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 88cffdbe..ac2d7002 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -45,6 +45,7 @@ type CacheFile struct { storeRDRC bool rdrcTimeout time.Duration DB *bbolt.DB + resetAccess sync.Mutex saveMetadataTimer *time.Timer saveFakeIPAccess sync.RWMutex saveDomain map[netip.Addr]string @@ -169,13 +170,55 @@ func (c *CacheFile) Close() error { return c.DB.Close() } +func (c *CacheFile) view(fn func(tx *bbolt.Tx) error) (err error) { + defer func() { + if r := recover(); r != nil { + c.resetDB() + err = E.New("database corrupted: ", r) + } + }() + return c.DB.View(fn) +} + +func (c *CacheFile) batch(fn func(tx *bbolt.Tx) error) (err error) { + defer func() { + if r := recover(); r != nil { + c.resetDB() + err = E.New("database corrupted: ", r) + } + }() + return c.DB.Batch(fn) +} + +func (c *CacheFile) update(fn func(tx *bbolt.Tx) error) (err error) { + defer func() { + if r := recover(); r != nil { + c.resetDB() + err = E.New("database corrupted: ", r) + } + }() + return c.DB.Update(fn) +} + +func (c *CacheFile) resetDB() { + c.resetAccess.Lock() + defer c.resetAccess.Unlock() + c.DB.Close() + os.Remove(c.path) + db, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second}) + if err == nil { + _ = filemanager.Chown(c.ctx, c.path) + c.DB = db + } +} + func (c *CacheFile) StoreFakeIP() bool { return c.storeFakeIP } func (c *CacheFile) LoadMode() string { var mode string - c.DB.View(func(t *bbolt.Tx) error { + c.view(func(t *bbolt.Tx) error { bucket := t.Bucket(bucketMode) if bucket == nil { return nil @@ -193,7 +236,7 @@ func (c *CacheFile) LoadMode() string { } func (c *CacheFile) StoreMode(mode string) error { - return c.DB.Batch(func(t *bbolt.Tx) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := t.CreateBucketIfNotExists(bucketMode) if err != nil { return err @@ -230,7 +273,7 @@ func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error) func (c *CacheFile) LoadSelected(group string) string { var selected string - c.DB.View(func(t *bbolt.Tx) error { + c.view(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketSelected) if bucket == nil { return nil @@ -245,7 +288,7 @@ func (c *CacheFile) LoadSelected(group string) string { } func (c *CacheFile) StoreSelected(group, selected string) error { - return c.DB.Batch(func(t *bbolt.Tx) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketSelected) if err != nil { return err @@ -255,7 +298,7 @@ func (c *CacheFile) StoreSelected(group, selected string) error { } func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) { - c.DB.View(func(t *bbolt.Tx) error { + c.view(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketExpand) if bucket == nil { return nil @@ -271,7 +314,7 @@ func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) { } func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { - return c.DB.Batch(func(t *bbolt.Tx) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketExpand) if err != nil { return err @@ -286,7 +329,7 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary { var savedSet adapter.SavedBinary - err := c.DB.View(func(t *bbolt.Tx) error { + err := c.view(func(t *bbolt.Tx) error { bucket := c.bucket(t, bucketRuleSet) if bucket == nil { return os.ErrNotExist @@ -304,7 +347,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary { } func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error { - return c.DB.Batch(func(t *bbolt.Tx) error { + return c.batch(func(t *bbolt.Tx) error { bucket, err := c.createBucket(t, bucketRuleSet) if err != nil { return err diff --git a/experimental/cachefile/fakeip.go b/experimental/cachefile/fakeip.go index 8fe0f113..7a4bd384 100644 --- a/experimental/cachefile/fakeip.go +++ b/experimental/cachefile/fakeip.go @@ -23,7 +23,7 @@ var ( func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata { var metadata adapter.FakeIPMetadata - err := c.DB.Batch(func(tx *bbolt.Tx) error { + err := c.batch(func(tx *bbolt.Tx) error { bucket := tx.Bucket(bucketFakeIP) if bucket == nil { return os.ErrNotExist @@ -45,7 +45,7 @@ func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata { } func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error { - return c.DB.Batch(func(tx *bbolt.Tx) error { + return c.batch(func(tx *bbolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP) if err != nil { return err @@ -69,7 +69,7 @@ func (c *CacheFile) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) { } func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error { - return c.DB.Batch(func(tx *bbolt.Tx) error { + return c.batch(func(tx *bbolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP) if err != nil { return err @@ -136,7 +136,7 @@ func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) { return cachedDomain, true } var domain string - _ = c.DB.View(func(tx *bbolt.Tx) error { + _ = c.view(func(tx *bbolt.Tx) error { bucket := tx.Bucket(bucketFakeIP) if bucket == nil { return nil @@ -163,7 +163,7 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo return cachedAddress, true } var address netip.Addr - _ = c.DB.View(func(tx *bbolt.Tx) error { + _ = c.view(func(tx *bbolt.Tx) error { var bucket *bbolt.Bucket if isIPv6 { bucket = tx.Bucket(bucketFakeIPDomain6) @@ -180,7 +180,7 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo } func (c *CacheFile) FakeIPReset() error { - return c.DB.Batch(func(tx *bbolt.Tx) error { + return c.batch(func(tx *bbolt.Tx) error { err := tx.DeleteBucket(bucketFakeIP) if err != nil { return err diff --git a/experimental/cachefile/rdrc.go b/experimental/cachefile/rdrc.go index c4800951..d27ea8b2 100644 --- a/experimental/cachefile/rdrc.go +++ b/experimental/cachefile/rdrc.go @@ -31,7 +31,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) ( copy(key[2:], qName) defer buf.Put(key) var deleteCache bool - err := c.DB.View(func(tx *bbolt.Tx) error { + err := c.view(func(tx *bbolt.Tx) error { bucket := c.bucket(tx, bucketRDRC) if bucket == nil { return nil @@ -56,7 +56,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) ( return } if deleteCache { - c.DB.Update(func(tx *bbolt.Tx) error { + c.update(func(tx *bbolt.Tx) error { bucket := c.bucket(tx, bucketRDRC) if bucket == nil { return nil @@ -72,7 +72,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) ( } func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error { - return c.DB.Batch(func(tx *bbolt.Tx) error { + return c.batch(func(tx *bbolt.Tx) error { bucket, err := c.createBucket(tx, bucketRDRC) if err != nil { return err From d8e269e0ac38d54f6f99208dab43568a7c3249c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Feb 2026 22:00:42 +0800 Subject: [PATCH 126/185] socks: Fix "Fix missing UDP timeout" --- go.mod | 2 +- go.sum | 4 +- test/go.mod | 79 +++++++++++---------- test/go.sum | 168 +++++++++++++++++++++------------------------ test/socks_test.go | 134 ++++++++++++++++++++++++++++++++++++ 5 files changed, 254 insertions(+), 133 deletions(-) create mode 100644 test/socks_test.go diff --git a/go.mod b/go.mod index d7697105..ff1afeb3 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 - github.com/sagernet/sing v0.8.0-beta.15.0.20260202162209-7c477e13f41e + github.com/sagernet/sing v0.8.0-beta.16 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 375d6038..ddc9237e 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.15.0.20260202162209-7c477e13f41e h1:H8izpW6d9l8Ub5UFSV/Q2WCehss2KAlmnDiABa4BHp0= -github.com/sagernet/sing v0.8.0-beta.15.0.20260202162209-7c477e13f41e/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA= +github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= diff --git a/test/go.mod b/test/go.mod index ee69f102..7d6d9edc 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,9 +10,9 @@ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/gofrs/uuid/v5 v5.4.0 - github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 - github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 - github.com/sagernet/sing-quic v0.6.0-beta.6 + github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 + github.com/sagernet/sing v0.8.0-beta.16 + github.com/sagernet/sing-quic v0.6.0-beta.11 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/spyzhov/ajson v0.9.4 @@ -40,17 +40,17 @@ require ( github.com/database64128/tfo-go/v2 v2.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect - github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.9.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/florianl/go-nfqueue/v2 v2.0.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gaissmai/bart v0.18.0 // indirect github.com/go-chi/chi/v5 v5.2.3 // indirect github.com/go-chi/render v1.0.3 // indirect - github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect + github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -65,21 +65,19 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/illarion/gonotify/v3 v3.0.2 // indirect github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/keybase/go-keychain v0.0.1 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/libdns/acmedns v0.5.0 // indirect github.com/libdns/alidns v1.0.6-beta.3 // indirect github.com/libdns/cloudflare v0.2.2 // indirect github.com/libdns/libdns v1.1.1 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect - github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect - github.com/mdlayher/sdnotify v1.0.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect - github.com/metacubex/utls v1.8.3 // indirect + github.com/metacubex/utls v1.8.4 // indirect github.com/mholt/acmez/v3 v3.1.4 // indirect github.com/miekg/dns v1.1.69 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect @@ -88,8 +86,9 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/openai/openai-go/v3 v3.15.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pires/go-proxyproto v0.8.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect @@ -97,40 +96,40 @@ require ( github.com/safchain/ethtool v0.3.0 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cors v1.2.1 // indirect - github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a // indirect - github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e // indirect + github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 // indirect + github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f // indirect github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect - github.com/sagernet/sing-mux v0.3.3 // indirect + github.com/sagernet/sing-mux v0.3.4 // indirect github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect - github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e // indirect + github.com/sagernet/sing-tun v0.8.0-beta.17 // indirect github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect - github.com/sagernet/smux v1.5.34-mod.2 // indirect - github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 // indirect + github.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 // indirect github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect @@ -140,7 +139,6 @@ require ( github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect - github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -163,6 +161,7 @@ require ( golang.org/x/crypto v0.46.0 // indirect golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect golang.org/x/mod v0.31.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.38.0 // indirect diff --git a/test/go.sum b/test/go.sum index a7725af2..34f8d997 100644 --- a/test/go.sum +++ b/test/go.sum @@ -37,13 +37,10 @@ github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw= github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= -github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= @@ -56,6 +53,8 @@ github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE= +github.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -68,8 +67,8 @@ github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= -github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= +github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -105,8 +104,6 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= -github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU= github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= @@ -115,14 +112,16 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE= +github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ= github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8= github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= @@ -131,16 +130,12 @@ github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= -github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= +github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= +github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= @@ -159,10 +154,12 @@ github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1va github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= 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/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -180,54 +177,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a h1:EdIrRa0yH3ZaLOfX95RYYiN0etpX3b9+jcsW/D8jCyQ= -github.com/sagernet/cronet-go v0.0.0-20251220122645-b05b5c41614a/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a h1:GU/oBPVjyrCVb2l0SI5qtF6aUlLsnE4u4ehXbS/NF+0= -github.com/sagernet/cronet-go/all v0.0.0-20251220122645-b05b5c41614a/go.mod h1:4MT0juyKK0lIVwa6+6xLbRMFJBUIqRZOx70iMvq5vf0= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e h1:EAcY0ZK8DWTNUdVCKdBQfXRsfGsohsh2ae9jIjlri2o= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:9luoNSy9Ej8rC/nZejzrP0uxX+BxiNxjLJ7XPYlApQg= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e h1:CSWZTuMlkO/98RSQpqC1EHtc9eSBzVmvMdPTnaFSDCM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:vkf3GDA11NtGm6XQHyP4tgWK3GD426ObmshdKdxGMhA= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:pNyNjDC5XPC6+2yNqiDA8QL8IYSsBJpaSLwPi/jY4JQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:2yDmTzppvxWuwxo4oDjxRzjlXBzZ6O3OCPWYeQcJRrw= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:+lyrRlxlKBYT/3LcYMaFHilUnRlKn3OQrImlWCey+qM= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:dBeeUaCPG0Y26FfCPO1o3v72yw2pSjqopryAW0uV354= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:JkGEqkfeglehN9tu+a0gHTq9Dmm+pY2wtjY+NiUdjgw= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e h1:rd/AbuuEMCHcA6ib5JBkQmWvonnoGwVc0/P0sHcWfAQ= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e h1:4XcwU6KOCkVOAr2EKKY8geYm7ctKCLlb1SsmFWMMUzE= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:fYDhPtxvMtZxBUM0k6GcaxbynLxMG2iTmQL3XcLtjS8= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:F5Vzd50H/AaoSHvt81yXZeUwDRKTcFjO/+KVW6VqbUs= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e h1:Ok/Eh8ajcvxlu7FAWk+lHAPLcSRDKrKkgq30o6gCR6M= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:JoohVAfcwE9HjpwAaTULtEOFZ1Mz0Zz5K4pOcrRjqwo= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e h1:ULxfoxcNRA96QDDRLmObH4adbeUGtudH/2HlL4TgaNY= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e h1:NnhScXJyx4Fg+pV/WtkC1mD6+VR7D/Q+PMU4xGRvdRQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:INcIfjzUl0qgtZkt8OcVV537GoApcBsdegDl8yNJEdQ= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:jrialJ4U7BW9xyGI/JdUiigZ9hgyF1EayFKCH4pZxdw= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e h1:XefHhnALVHU4ZgICNq6ZJl78bWDQO4nHwoWn+Ar7e0g= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e h1:djShWO5vN0CamyboNDyNRDxL0/9oMtKjCDesqolIAsg= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e h1:AgKqyS5zfRgDc8uprxUh+XCwwzJCj31KAFPjzyLRWs0= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251220122226-25b6d00c5b7e/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 h1:0BYNmr0ptjsII948U0oBFmrbo4qEaCFcrE2JPRg3Zlk= +github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 h1:ghxhYSBQpzkakqWqJDvXr/Zmxe0WjTjKuALEGbjGiGY= +github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:M+4ZjPhLJXIvoxcQsbDofmc19Wrig59hZ+hLvj6S3To= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f h1:8jZbZ4KBTdcXDFLwUBNQt5Xci6ZuAKh255S8TwuBCaM= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f h1:tG0hCx+0u5zca7qQ7AMkcv4DCrBG/DKW1ggs/P+BRRI= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f h1:ZXp5hKJIA7iJ52ZShJCKMQEPLpp/7dDIVZmPGV9Il40= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f h1:gL7H8HS8s38adz4/HZtRHh79qMwsbLTRRPz4GQ9LcWI= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f h1:Dchgc0pAY5Jwb5lzUlE+1nhHIzqLx+YOurXLHgvWd/0= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f h1:+MOLSQoduuKDxF410i1LcSPaQGaiP0eZb0INvMlmjM4= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:lIZna05Vn6n8k21p8OpSUnhwGm+E57PrMjiI4ZUfMSg= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f h1:B2aFQ5CRHI20t8YsEizvtguS5W2QfK7D5XV/NzTIxPE= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:qpSwJ1rFGYCfJDenNCZoWYjoG7N+xEa6ke+E7/JO1i4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f h1:cx7Ipg0tSvTDjS4maMEYz4vuzz93BMPAysmZ1YLrz80= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f h1:4jOHuUiBxD8pJEpBBVQfJqyLmxjpd3t4MLRzU7YLFyg= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f h1:OpXBa2WlRU+Mam9oRe9Nn4/zf7gQ+qiBTNK8A5RwbfQ= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f h1:nJpGFi+6hI85tl4zoyNFEnFEQ5+xEV5gyvsUoMvd8g0= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f h1:SEy2rpmgOJgrqcEryJI/RSnqUWIsEsp0cfYoA8y21jc= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f h1:EW2TuFMLm0iBGqRZtuGwIZdeYmDtDsDmRcRRJQOMxUo= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f h1:3U5woxrNCkzfv1+UX+mVoWh1228AE1qAiMG02F9oFbY= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f h1:YwFTfuWG3mmctroeDYtFZ6LHjGsedVO+5wInYbbUuUY= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:r4V0ddPCRLgGu0VdgR3aUsO9NjpmyjAf+h+3oTD9D6E= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f h1:B8yf4gFvEYUnwWmtVK9sdwUsflYZ387MhYmlOP2ohFQ= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:9YyaMg4rO1/jIgrxmNb0LKH+X7frSYWfX2pFgW5JUVM= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f h1:B0fnGu0sh9yT/9JDN5u/GqThGoOzNN/daOAuGWFLXEk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f h1:lxPcIXKSSI5JDhc7rx/6yufISWM4vtBS2FY9PavWQTs= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU= @@ -236,27 +233,28 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.58.0-sing-box-mod.1 h1:E9yZrU0ZxSiW5RrGUnFZeI02EIMdAAv0RxdoxXCqZyk= -github.com/sagernet/quic-go v0.58.0-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= -github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= -github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA= +github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= +github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= +github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= +github.com/sagernet/sing-quic v0.6.0-beta.11/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e h1:ZEv+9vy7vC1vbr3LfwZGx3JAOkl/w4+hnGamHw4W36M= -github.com/sagernet/sing-tun v0.8.0-beta.11.0.20251201004738-e9e3fbf0c15e/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg= +github.com/sagernet/sing-tun v0.8.0-beta.17 h1:6DdbNXeTFYj8Tb4FCh8Mp2boA3rVY6VNqzTOObj7Xis= +github.com/sagernet/sing-tun v0.8.0-beta.17/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= -github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= -github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= -github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4 h1:Ceg+9Ug+qAFgEchGodlHmMOY2h7KktQQDAyuoIsPbos= -github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.4/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw= +github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= +github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= @@ -266,14 +264,7 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84= github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= @@ -290,8 +281,6 @@ github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+y github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= -github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= -github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -373,6 +362,8 @@ 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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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= @@ -388,7 +379,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -433,8 +423,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= -gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= diff --git a/test/socks_test.go b/test/socks_test.go new file mode 100644 index 00000000..63dca5d6 --- /dev/null +++ b/test/socks_test.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "net" + "net/netip" + "testing" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/json/badoption" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/socks" + + "github.com/stretchr/testify/require" +) + +func TestSOCKSUDPTimeout(t *testing.T) { + const testTimeout = 2 * time.Second + udpTimeout := option.UDPTimeoutCompat(testTimeout) + + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeSOCKS, + Tag: "socks-in", + Options: &option.SocksInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + UDPTimeout: udpTimeout, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + }, + }) + + testUDPSessionIdleTimeout(t, clientPort, testPort, testTimeout) +} + +func TestMixedUDPTimeout(t *testing.T) { + const testTimeout = 2 * time.Second + udpTimeout := option.UDPTimeoutCompat(testTimeout) + + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + Options: &option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), + ListenPort: clientPort, + UDPTimeout: udpTimeout, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + }, + }) + + testUDPSessionIdleTimeout(t, clientPort, testPort, testTimeout) +} + +func testUDPSessionIdleTimeout(t *testing.T, proxyPort uint16, echoPort uint16, expectedTimeout time.Duration) { + echoServer, err := listenPacket("udp", ":"+F.ToString(echoPort)) + require.NoError(t, err) + defer echoServer.Close() + + go func() { + buffer := make([]byte, 1024) + for { + n, address, err := echoServer.ReadFrom(buffer) + if err != nil { + return + } + _, _ = echoServer.WriteTo(buffer[:n], address) + } + }() + + dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", proxyPort), socks.Version5, "", "") + + packetConn, err := dialer.ListenPacket(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) + require.NoError(t, err) + defer packetConn.Close() + + remoteAddress := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: int(echoPort)} + + _, err = packetConn.WriteTo([]byte("hello"), remoteAddress) + require.NoError(t, err) + + buffer := make([]byte, 1024) + packetConn.SetReadDeadline(time.Now().Add(5 * time.Second)) + n, _, err := packetConn.ReadFrom(buffer) + require.NoError(t, err, "failed to receive echo response") + require.Equal(t, "hello", string(buffer[:n])) + t.Log("UDP echo successful, session established") + + packetConn.SetReadDeadline(time.Time{}) + + waitTime := expectedTimeout + time.Second + t.Logf("Waiting %v for UDP session to timeout...", waitTime) + time.Sleep(waitTime) + + _, err = packetConn.WriteTo([]byte("after-timeout"), remoteAddress) + if err != nil { + t.Logf("Write after timeout correctly failed: %v", err) + return + } + + packetConn.SetReadDeadline(time.Now().Add(3 * time.Second)) + n, _, err = packetConn.ReadFrom(buffer) + + if err != nil { + t.Logf("Read after timeout correctly failed: %v", err) + return + } + + t.Fatalf("UDP session should have timed out after %v, but received response: %s", + expectedTimeout, string(buffer[:n])) +} From aba8346bd6c533ffb144258118e1100ff31e2cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Feb 2026 22:28:30 +0800 Subject: [PATCH 127/185] Fix DNS cache lock goroutine leak The cache deduplication in Client.Exchange uses a channel-based lock per DNS question. Waiting goroutines blocked on <-cond without context awareness, causing them to accumulate indefinitely when the owning goroutine's transport call stalls. Add select on ctx.Done() so waiters respect context cancellation and timeouts. --- dns/client.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dns/client.go b/dns/client.go index 939ca48c..2982d11c 100644 --- a/dns/client.go +++ b/dns/client.go @@ -144,7 +144,11 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m if c.cache != nil { cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{})) if loaded { - <-cond + select { + case <-cond: + case <-ctx.Done(): + return nil, ctx.Err() + } } else { defer func() { c.cacheLock.Delete(question) @@ -154,7 +158,11 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m } else if c.transportCache != nil { cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{})) if loaded { - <-cond + select { + case <-cond: + case <-ctx.Done(): + return nil, ctx.Err() + } } else { defer func() { c.transportCacheLock.Delete(question) From 172a9d5e4ed330e54282ff9a6e40def20e3a0ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 7 Feb 2026 08:19:24 +0800 Subject: [PATCH 128/185] Standardize gomobile usages --- daemon/instance.go | 2 ++ experimental/libbox/command_client.go | 8 +++---- experimental/libbox/command_server.go | 2 +- experimental/libbox/command_types.go | 30 +++++++++++++-------------- experimental/libbox/config.go | 6 +++--- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/daemon/instance.go b/daemon/instance.go index 9bdce84c..5947452f 100644 --- a/daemon/instance.go +++ b/daemon/instance.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" @@ -103,6 +104,7 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove i.clashServer = service.FromContext[adapter.ClashServer](ctx) i.pauseManager = service.FromContext[pause.Manager](ctx) i.cacheFile = service.FromContext[adapter.CacheFile](ctx) + log.SetStdLogger(boxInstance.LogFactory().Logger()) return i, nil } diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index f0788d7a..84fe9595 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -362,7 +362,7 @@ func (c *CommandClient) handleStatusStream() { c.handler.Disconnected(err.Error()) return } - c.handler.WriteStatus(StatusMessageFromGRPC(status)) + c.handler.WriteStatus(statusMessageFromGRPC(status)) } } @@ -381,7 +381,7 @@ func (c *CommandClient) handleGroupStream() { c.handler.Disconnected(err.Error()) return } - c.handler.WriteGroups(OutboundGroupIteratorFromGRPC(groups)) + c.handler.WriteGroups(outboundGroupIteratorFromGRPC(groups)) } } @@ -447,7 +447,7 @@ func (c *CommandClient) handleConnectionsStream() { c.handler.Disconnected(err.Error()) return } - libboxEvents := ConnectionEventsFromGRPC(events) + libboxEvents := connectionEventsFromGRPC(events) c.handler.WriteConnectionEvents(libboxEvents) } } @@ -523,7 +523,7 @@ func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) { if err != nil { return nil, err } - return SystemProxyStatusFromGRPC(status), nil + return systemProxyStatusFromGRPC(status), nil }) } diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index f889c381..2b4ac71b 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -43,7 +43,7 @@ type CommandServerHandler interface { } func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) { - ctx := BaseContext(platformInterface) + ctx := baseContext(platformInterface) platformWrapper := &platformInterfaceWrapper{ iif: platformInterface, useProcFS: platformInterface.UseProcFS(), diff --git a/experimental/libbox/command_types.go b/experimental/libbox/command_types.go index 1091b3f7..39027ac7 100644 --- a/experimental/libbox/command_types.go +++ b/experimental/libbox/command_types.go @@ -32,11 +32,11 @@ type OutboundGroup struct { Selectable bool Selected string IsExpand bool - ItemList []*OutboundGroupItem + itemList []*OutboundGroupItem } func (g *OutboundGroup) GetItems() OutboundGroupItemIterator { - return newIterator(g.ItemList) + return newIterator(g.itemList) } type OutboundGroupIterator interface { @@ -267,12 +267,12 @@ type Connection struct { Rule string Outbound string OutboundType string - ChainList []string + chainList []string ProcessInfo *ProcessInfo } func (c *Connection) Chain() StringIterator { - return newIterator(c.ChainList) + return newIterator(c.chainList) } func (c *Connection) DisplayDestination() string { @@ -292,7 +292,7 @@ type ConnectionIterator interface { HasNext() bool } -func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage { +func statusMessageFromGRPC(status *daemon.Status) *StatusMessage { if status == nil { return nil } @@ -309,7 +309,7 @@ func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage { } } -func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator { +func outboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator { if groups == nil || len(groups.Group) == 0 { return newIterator([]*OutboundGroup{}) } @@ -323,7 +323,7 @@ func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator IsExpand: g.IsExpand, } for _, item := range g.Items { - libboxGroup.ItemList = append(libboxGroup.ItemList, &OutboundGroupItem{ + libboxGroup.itemList = append(libboxGroup.itemList, &OutboundGroupItem{ Tag: item.Tag, Type: item.Type, URLTestTime: item.UrlTestTime, @@ -335,7 +335,7 @@ func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator return newIterator(libboxGroups) } -func ConnectionFromGRPC(conn *daemon.Connection) Connection { +func connectionFromGRPC(conn *daemon.Connection) Connection { var processInfo *ProcessInfo if conn.ProcessInfo != nil { processInfo = &ProcessInfo{ @@ -367,12 +367,12 @@ func ConnectionFromGRPC(conn *daemon.Connection) Connection { Rule: conn.Rule, Outbound: conn.Outbound, OutboundType: conn.OutboundType, - ChainList: conn.ChainList, + chainList: conn.ChainList, ProcessInfo: processInfo, } } -func ConnectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent { +func connectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent { if event == nil { return nil } @@ -384,13 +384,13 @@ func ConnectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent { ClosedAt: event.ClosedAt, } if event.Connection != nil { - conn := ConnectionFromGRPC(event.Connection) + conn := connectionFromGRPC(event.Connection) libboxEvent.Connection = &conn } return libboxEvent } -func ConnectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents { +func connectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents { if events == nil { return nil } @@ -398,14 +398,14 @@ func ConnectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents Reset: events.Reset_, } for _, event := range events.Events { - if libboxEvent := ConnectionEventFromGRPC(event); libboxEvent != nil { + if libboxEvent := connectionEventFromGRPC(event); libboxEvent != nil { libboxEvents.events = append(libboxEvents.events, libboxEvent) } } return libboxEvents } -func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus { +func systemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus { if status == nil { return nil } @@ -415,7 +415,7 @@ func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxySta } } -func SystemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus { +func systemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus { if status == nil { return nil } diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index c89f3d69..122425d2 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -22,7 +22,7 @@ import ( "github.com/sagernet/sing/service/filemanager" ) -func BaseContext(platformInterface PlatformInterface) context.Context { +func baseContext(platformInterface PlatformInterface) context.Context { dnsRegistry := include.DNSTransportRegistry() if platformInterface != nil { if localTransport := platformInterface.LocalDNSTransport(); localTransport != nil { @@ -45,7 +45,7 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err } func CheckConfig(configContent string) error { - ctx := BaseContext(nil) + ctx := baseContext(nil) options, err := parseConfig(ctx, configContent) if err != nil { return err @@ -189,7 +189,7 @@ func (s *interfaceMonitorStub) MyInterface() string { } func FormatConfig(configContent string) (*StringBox, error) { - options, err := parseConfig(BaseContext(nil), configContent) + options, err := parseConfig(baseContext(nil), configContent) if err != nil { return nil, err } From 98af3c0ad63ec4add436d16c21554b3b8b8a70f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 9 Feb 2026 13:33:15 +0800 Subject: [PATCH 129/185] experimental: New FFI --- Makefile | 14 +- cmd/internal/build_libbox_newffi/main.go | 93 +++++++++++++ experimental/libbox/ffi.json | 167 +++++++++++++++++++++++ 3 files changed, 265 insertions(+), 9 deletions(-) create mode 100644 cmd/internal/build_libbox_newffi/main.go create mode 100644 experimental/libbox/ffi.json diff --git a/Makefile b/Makefile index 5f1408b8..b94af10c 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,7 @@ update_apple_version: update_macos_version: MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version -release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone +release_apple: lib_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone release_apple_beta: update_apple_version release_ios release_macos release_tvos @@ -234,18 +234,14 @@ test_stdio: lib_android: go run ./cmd/internal/build_libbox -target android -lib_android_debug: - go run ./cmd/internal/build_libbox -target android -debug - lib_apple: go run ./cmd/internal/build_libbox -target apple -lib_ios: - go run ./cmd/internal/build_libbox -target apple -platform ios -debug +lib_android_new: + go run ./cmd/internal/build_libbox_newffi -target android -lib: - go run ./cmd/internal/build_libbox -target android - go run ./cmd/internal/build_libbox -target ios +lib_apple_new: + go run ./cmd/internal/build_libbox_newffi -target apple lib_install: go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11 diff --git a/cmd/internal/build_libbox_newffi/main.go b/cmd/internal/build_libbox_newffi/main.go new file mode 100644 index 00000000..4df1d465 --- /dev/null +++ b/cmd/internal/build_libbox_newffi/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "flag" + "os" + "os/exec" + "path/filepath" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/rw" +) + +var target string + +func init() { + flag.StringVar(&target, "target", "android", "target platform (android or apple)") +} + +func main() { + flag.Parse() + + args := []string{ + "generate", + "-v", + "--config", "experimental/libbox/ffi.json", + "--platform-type", target, + } + command := exec.Command("sing-ffi", args...) + command.Stdout = os.Stdout + command.Stderr = os.Stderr + err := command.Run() + if err != nil { + log.Fatal(err) + } + + copyArtifacts(target) +} + +func copyArtifacts(target string) { + switch target { + case "android": + copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs") + if rw.IsDir(copyPath) { + copyPath, _ = filepath.Abs(copyPath) + for _, name := range []string{"libbox.aar", "libbox-legacy.aar"} { + artifactPath, found := findArtifactPath(name) + if !found { + continue + } + targetPath := filepath.Join(target, artifactPath) + os.RemoveAll(targetPath) + err := os.Rename(artifactPath, targetPath) + if err != nil { + log.Fatal(err) + } + log.Info("copied ", name, " to ", copyPath) + } + } + case "apple": + copyPath := filepath.Join("..", "sing-box-for-apple") + if rw.IsDir(copyPath) { + sourceDir, found := findArtifactPath("Libbox.xcframework") + if !found { + log.Fatal("Libbox.xcframework not found in current directory or experimental/libbox") + } + + targetDir := filepath.Join(copyPath, "Libbox.xcframework") + targetDir, _ = filepath.Abs(targetDir) + err := os.RemoveAll(targetDir) + if err != nil { + log.Fatal(err) + } + err = os.Rename(sourceDir, targetDir) + if err != nil { + log.Fatal(err) + } + log.Info("copied ", sourceDir, " to ", targetDir) + } + } +} + +func findArtifactPath(name string) (string, bool) { + candidates := []string{ + name, + filepath.Join("experimental", "libbox", name), + } + for _, candidate := range candidates { + if rw.IsFile(candidate) || rw.IsDir(candidate) { + return candidate, true + } + } + return "", false +} diff --git a/experimental/libbox/ffi.json b/experimental/libbox/ffi.json new file mode 100644 index 00000000..5c5c72d5 --- /dev/null +++ b/experimental/libbox/ffi.json @@ -0,0 +1,167 @@ +{ + "version": 1, + "packages": [ + { + "id": "libbox", + "path": ".", + "java_package": "io.nekohasekai.libbox", + "apple_prefix": "Libbox" + } + ], + "builds": [ + { + "id": "android-main", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_naive_outbound", + "with_clash_api", + "with_conntrack", + "badlinkname", + "tfogo_checklinkname0", + "with_tailscale", + "ts_omit_logtail", + "ts_omit_ssh", + "ts_omit_drive", + "ts_omit_taildrop", + "ts_omit_webclient", + "ts_omit_doctor", + "ts_omit_capture", + "ts_omit_kube", + "ts_omit_aws", + "ts_omit_synology", + "ts_omit_bird" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + } + }, + { + "id": "android-legacy", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_clash_api", + "with_conntrack", + "badlinkname", + "tfogo_checklinkname0", + "with_tailscale", + "ts_omit_logtail", + "ts_omit_ssh", + "ts_omit_drive", + "ts_omit_taildrop", + "ts_omit_webclient", + "ts_omit_doctor", + "ts_omit_capture", + "ts_omit_kube", + "ts_omit_aws", + "ts_omit_synology", + "ts_omit_bird" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + } + }, + { + "id": "apple", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_naive_outbound", + "with_clash_api", + "with_conntrack", + "badlinkname", + "tfogo_checklinkname0", + "with_dhcp", + "grpcnotrace", + "with_tailscale", + "ts_omit_logtail", + "ts_omit_ssh", + "ts_omit_drive", + "ts_omit_taildrop", + "ts_omit_webclient", + "ts_omit_doctor", + "ts_omit_capture", + "ts_omit_kube", + "ts_omit_aws", + "ts_omit_synology", + "ts_omit_bird" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + }, + "overrides": [ + { + "match": { "os": "ios" }, + "tags_append": ["with_low_memory"] + }, + { + "match": { "os": "tvos" }, + "tags_append": ["with_low_memory"] + } + ] + } + ], + "platforms": [ + { + "type": "android", + "build": "android-main", + "min_sdk": 23, + "lib_name": "box", + "languages": [{ "type": "java" }], + "artifacts": [ + { + "type": "aar", + "output_path": "libbox.aar" + } + ] + }, + { + "type": "android", + "build": "android-legacy", + "min_sdk": 21, + "lib_name": "box", + "languages": [{ "type": "java" }], + "artifacts": [ + { + "type": "aar", + "output_path": "libbox-legacy.aar" + } + ] + }, + { + "type": "apple", + "build": "apple", + "targets": [ + "ios/arm64", + "ios/simulator/arm64", + "ios/simulator/amd64", + "tvos/arm64", + "tvos/simulator/arm64", + "tvos/simulator/amd64", + "macos/arm64", + "macos/amd64" + ], + "languages": [{ "type": "objc" }], + "artifacts": [ + { + "type": "xcframework", + "module_name": "Libbox", + "output_path": "Libbox.xcframework" + } + ] + } + ] +} From 58fcdceca24f113b8c5c2c41ed26e360e81e05a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 9 Feb 2026 13:46:53 +0800 Subject: [PATCH 130/185] Fix naive padding --- protocol/naive/inbound_conn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/naive/inbound_conn.go b/protocol/naive/inbound_conn.go index 1dbb2bd8..0711b637 100644 --- a/protocol/naive/inbound_conn.go +++ b/protocol/naive/inbound_conn.go @@ -95,6 +95,7 @@ func (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, er binary.BigEndian.PutUint16(header, uint16(len(data))) header[2] = byte(paddingSize) common.Must1(buffer.Write(data)) + buffer.Extend(paddingSize) _, err = writer.Write(buffer.Bytes()) if err == nil { n = len(data) From ceab24432911a49ec05334b022991b44f020aa1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 9 Feb 2026 13:59:05 +0800 Subject: [PATCH 131/185] tuic: Fix udp context --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ff1afeb3..8fe1ea40 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 github.com/sagernet/sing v0.8.0-beta.16 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.11 + github.com/sagernet/sing-quic v0.6.0-beta.12 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index ddc9237e..c428268f 100644 --- a/go.sum +++ b/go.sum @@ -214,8 +214,8 @@ github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5 github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4= -github.com/sagernet/sing-quic v0.6.0-beta.11/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= +github.com/sagernet/sing-quic v0.6.0-beta.12 h1:njyU2NYGBITShAu31wJRmqAtx7hQBcXqBPowDv+W0sk= +github.com/sagernet/sing-quic v0.6.0-beta.12/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= From 58ccf82e0bfc5e724421d2ba03157fe7f0fee537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 9 Feb 2026 14:41:13 +0800 Subject: [PATCH 132/185] Bump version --- clients/android | 2 +- docs/changelog.md | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/clients/android b/clients/android index 02f9ec4d..6491eff6 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 02f9ec4d9723cc7f6be7af570baf82eb261d8656 +Subproject commit 6491eff61e0bc71d545d4a620f8a697f2a90f839 diff --git a/docs/changelog.md b/docs/changelog.md index d42d6b03..616b47af 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.13.0-rc.2 +#### 1.13.0-rc.3 * Fixes and improvements @@ -169,6 +169,14 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; use NaiveProxy instead for TLS fingerprint resistance. +#### 1.12.21 + +* Fixes and improvements + +#### 1.13.0-rc.2 + +* Fixes and improvements + #### 1.12.20 * Fixes and improvements From 0a69621207cf44aa14554a0f60bb11c70c2bef6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 13 Feb 2026 00:50:08 +0800 Subject: [PATCH 133/185] wireguard: Fix missing fallback for gso --- transport/wireguard/device_system.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 162a5cbf..dcf2959b 100644 --- a/transport/wireguard/device_system.go +++ b/transport/wireguard/device_system.go @@ -116,7 +116,7 @@ func (w *systemDevice) Start() error { w.options.Logger.Info("started at ", w.options.Name) w.device = tunInterface batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN) - if isBatchTUN { + if isBatchTUN && batchTUN.BatchSize() > 1 { w.batchDevice = batchTUN } w.events <- wgTun.EventUp From 657fba4ca5fae0f7341862e97972bb27b8a5b1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 14 Feb 2026 01:59:53 +0800 Subject: [PATCH 134/185] Fix matching rule-set invert --- route/rule/rule_abstract.go | 4 +- route/rule/rule_abstract_test.go | 157 +++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 route/rule/rule_abstract_test.go diff --git a/route/rule/rule_abstract.go b/route/rule/rule_abstract.go index 5be215e0..45d5b893 100644 --- a/route/rule/rule_abstract.go +++ b/route/rule/rule_abstract.go @@ -107,9 +107,7 @@ func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool { } for _, item := range r.items { - if _, isRuleSet := item.(*RuleSetItem); !isRuleSet { - metadata.DidMatch = true - } + metadata.DidMatch = true if !item.Match(metadata) { return r.invert } diff --git a/route/rule/rule_abstract_test.go b/route/rule/rule_abstract_test.go new file mode 100644 index 00000000..2d2e8ba8 --- /dev/null +++ b/route/rule/rule_abstract_test.go @@ -0,0 +1,157 @@ +package rule + +import ( + "context" + "testing" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing/common/x/list" + + "github.com/stretchr/testify/require" + "go4.org/netipx" +) + +type fakeRuleSet struct { + matched bool +} + +func (f *fakeRuleSet) Name() string { + return "fake-rule-set" +} + +func (f *fakeRuleSet) StartContext(context.Context, *adapter.HTTPStartContext) error { + return nil +} + +func (f *fakeRuleSet) PostStart() error { + return nil +} + +func (f *fakeRuleSet) Metadata() adapter.RuleSetMetadata { + return adapter.RuleSetMetadata{} +} + +func (f *fakeRuleSet) ExtractIPSet() []*netipx.IPSet { + return nil +} + +func (f *fakeRuleSet) IncRef() {} + +func (f *fakeRuleSet) DecRef() {} + +func (f *fakeRuleSet) Cleanup() {} + +func (f *fakeRuleSet) RegisterCallback(adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] { + return nil +} + +func (f *fakeRuleSet) UnregisterCallback(*list.Element[adapter.RuleSetUpdateCallback]) {} + +func (f *fakeRuleSet) Close() error { + return nil +} + +func (f *fakeRuleSet) Match(*adapter.InboundContext) bool { + return f.matched +} + +func (f *fakeRuleSet) String() string { + return "fake-rule-set" +} + +type fakeRuleItem struct { + matched bool +} + +func (f *fakeRuleItem) Match(*adapter.InboundContext) bool { + return f.matched +} + +func (f *fakeRuleItem) String() string { + return "fake-rule-item" +} + +func newRuleSetOnlyRule(ruleSetMatched bool, invert bool) *DefaultRule { + ruleSetItem := &RuleSetItem{ + setList: []adapter.RuleSet{&fakeRuleSet{matched: ruleSetMatched}}, + } + return &DefaultRule{ + abstractDefaultRule: abstractDefaultRule{ + items: []RuleItem{ruleSetItem}, + allItems: []RuleItem{ruleSetItem}, + invert: invert, + }, + } +} + +func newSingleItemRule(matched bool) *DefaultRule { + item := &fakeRuleItem{matched: matched} + return &DefaultRule{ + abstractDefaultRule: abstractDefaultRule{ + items: []RuleItem{item}, + allItems: []RuleItem{item}, + }, + } +} + +func TestAbstractDefaultRule_RuleSetOnly_InvertFalse(t *testing.T) { + t.Parallel() + require.True(t, newRuleSetOnlyRule(true, false).Match(&adapter.InboundContext{})) + require.False(t, newRuleSetOnlyRule(false, false).Match(&adapter.InboundContext{})) +} + +func TestAbstractDefaultRule_RuleSetOnly_InvertTrue(t *testing.T) { + t.Parallel() + require.False(t, newRuleSetOnlyRule(true, true).Match(&adapter.InboundContext{})) + require.True(t, newRuleSetOnlyRule(false, true).Match(&adapter.InboundContext{})) +} + +func TestAbstractLogicalRule_And_WithRuleSetInvert(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + aMatched bool + ruleSetBMatch bool + expected bool + }{ + { + name: "A true B true", + aMatched: true, + ruleSetBMatch: true, + expected: false, + }, + { + name: "A true B false", + aMatched: true, + ruleSetBMatch: false, + expected: true, + }, + { + name: "A false B true", + aMatched: false, + ruleSetBMatch: true, + expected: false, + }, + { + name: "A false B false", + aMatched: false, + ruleSetBMatch: false, + expected: false, + }, + } + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + logicalRule := &abstractLogicalRule{ + mode: C.LogicalTypeAnd, + rules: []adapter.HeadlessRule{ + newSingleItemRule(testCase.aMatched), + newRuleSetOnlyRule(testCase.ruleSetBMatch, true), + }, + } + require.Equal(t, testCase.expected, logicalRule.Match(&adapter.InboundContext{})) + }) + } +} From 8714c157c91ceea1d37bb2a0b234049c53dffaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Feb 2026 19:19:47 +0800 Subject: [PATCH 135/185] Fix matching multi predefined --- dns/router.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dns/router.go b/dns/router.go index e82cab29..18b9e34d 100644 --- a/dns/router.go +++ b/dns/router.go @@ -377,9 +377,11 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ case *R.RuleActionReject: return nil, &R.RejectedError{Cause: action.Error(ctx)} case *R.RuleActionPredefined: + responseAddrs = nil if action.Rcode != mDNS.RcodeSuccess { err = RcodeError(action.Rcode) } else { + err = nil for _, answer := range action.Answer { switch record := answer.(type) { case *mDNS.A: From 1f2fdec89dd213873e25450e8687311d984f5df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Feb 2026 17:24:44 +0800 Subject: [PATCH 136/185] release: Fix update_apple_version command --- cmd/internal/update_apple_version/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/internal/update_apple_version/main.go b/cmd/internal/update_apple_version/main.go index 93388e49..1b2d0db5 100644 --- a/cmd/internal/update_apple_version/main.go +++ b/cmd/internal/update_apple_version/main.go @@ -71,12 +71,12 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}") versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20 versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";") - version := projectContent[versionStart:versionEnd] + version := strings.Trim(projectContent[versionStart:versionEnd], "\"") if version == newVersion { continue } updated = true - projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:] + projectContent = projectContent[:versionStart] + "\"" + newVersion + "\"" + projectContent[versionEnd:] } return projectContent, updated } From 53f2db3f97a56375b914fbf6cde07a7d6c62b22b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 12 Feb 2026 16:39:50 +0800 Subject: [PATCH 137/185] platform: Add windows build --- .gitignore | 3 + Makefile | 9 +- cmd/internal/build_libbox_newffi/main.go | 93 -------------------- experimental/libbox/command_client.go | 6 +- experimental/libbox/ffi.json | 106 +++++++++++++++++++++-- 5 files changed, 115 insertions(+), 102 deletions(-) delete mode 100644 cmd/internal/build_libbox_newffi/main.go diff --git a/.gitignore b/.gitignore index 5a964b24..d2b74d08 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ /*.jar /*.aar /*.xcframework/ +/experimental/libbox/*.aar +/experimental/libbox/*.xcframework/ +/experimental/libbox/*.nupkg .DS_Store /config.d/ /venv/ diff --git a/Makefile b/Makefile index b94af10c..88eb89c6 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,8 @@ PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Versio MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)" MAIN = ./cmd/sing-box PREFIX ?= $(shell go env GOPATH) +SING_FFI ?= sing-ffi +LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json .PHONY: test release docs build @@ -237,11 +239,14 @@ lib_android: lib_apple: go run ./cmd/internal/build_libbox -target apple +lib_windows: + $(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp + lib_android_new: - go run ./cmd/internal/build_libbox_newffi -target android + $(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android lib_apple_new: - go run ./cmd/internal/build_libbox_newffi -target apple + $(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple lib_install: go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11 diff --git a/cmd/internal/build_libbox_newffi/main.go b/cmd/internal/build_libbox_newffi/main.go deleted file mode 100644 index 4df1d465..00000000 --- a/cmd/internal/build_libbox_newffi/main.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "flag" - "os" - "os/exec" - "path/filepath" - - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common/rw" -) - -var target string - -func init() { - flag.StringVar(&target, "target", "android", "target platform (android or apple)") -} - -func main() { - flag.Parse() - - args := []string{ - "generate", - "-v", - "--config", "experimental/libbox/ffi.json", - "--platform-type", target, - } - command := exec.Command("sing-ffi", args...) - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err := command.Run() - if err != nil { - log.Fatal(err) - } - - copyArtifacts(target) -} - -func copyArtifacts(target string) { - switch target { - case "android": - copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs") - if rw.IsDir(copyPath) { - copyPath, _ = filepath.Abs(copyPath) - for _, name := range []string{"libbox.aar", "libbox-legacy.aar"} { - artifactPath, found := findArtifactPath(name) - if !found { - continue - } - targetPath := filepath.Join(target, artifactPath) - os.RemoveAll(targetPath) - err := os.Rename(artifactPath, targetPath) - if err != nil { - log.Fatal(err) - } - log.Info("copied ", name, " to ", copyPath) - } - } - case "apple": - copyPath := filepath.Join("..", "sing-box-for-apple") - if rw.IsDir(copyPath) { - sourceDir, found := findArtifactPath("Libbox.xcframework") - if !found { - log.Fatal("Libbox.xcframework not found in current directory or experimental/libbox") - } - - targetDir := filepath.Join(copyPath, "Libbox.xcframework") - targetDir, _ = filepath.Abs(targetDir) - err := os.RemoveAll(targetDir) - if err != nil { - log.Fatal(err) - } - err = os.Rename(sourceDir, targetDir) - if err != nil { - log.Fatal(err) - } - log.Info("copied ", sourceDir, " to ", targetDir) - } - } -} - -func findArtifactPath(name string) (string, bool) { - candidates := []string{ - name, - filepath.Join("experimental", "libbox", name), - } - for _, candidate := range candidates { - if rw.IsFile(candidate) || rw.IsDir(candidate) { - return candidate, true - } - } - return "", false -} diff --git a/experimental/libbox/command_client.go b/experimental/libbox/command_client.go index 84fe9595..a5077bea 100644 --- a/experimental/libbox/command_client.go +++ b/experimental/libbox/command_client.go @@ -119,7 +119,11 @@ func dialTarget() (string, func(context.Context, string) (net.Conn, error)) { } } if sCommandServerListenPort == 0 { - return "unix://" + filepath.Join(sBasePath, "command.sock"), nil + socketPath := filepath.Join(sBasePath, "command.sock") + return "passthrough:///command-socket", func(ctx context.Context, _ string) (net.Conn, error) { + var networkDialer net.Dialer + return networkDialer.DialContext(ctx, "unix", socketPath) + } } return net.JoinHostPort("127.0.0.1", strconv.Itoa(int(sCommandServerListenPort))), nil } diff --git a/experimental/libbox/ffi.json b/experimental/libbox/ffi.json index 5c5c72d5..28333871 100644 --- a/experimental/libbox/ffi.json +++ b/experimental/libbox/ffi.json @@ -1,10 +1,19 @@ { "version": 1, + "variables": { + "VERSION": "$(go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)", + "WORKSPACE_ROOT": "../../..", + "DEPLOY_ANDROID": "${WORKSPACE_ROOT}/sing-box-for-android/app/libs", + "DEPLOY_APPLE": "${WORKSPACE_ROOT}/sing-box-for-apple", + "DEPLOY_WINDOWS": "${WORKSPACE_ROOT}/sing-box-for-windows/local-packages" + }, "packages": [ { "id": "libbox", "path": ".", "java_package": "io.nekohasekai.libbox", + "csharp_namespace": "SagerNet", + "csharp_entrypoint": "Libbox", "apple_prefix": "Libbox" } ], @@ -36,7 +45,7 @@ "ts_omit_synology", "ts_omit_bird" ], - "ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", "trimpath": true } }, @@ -66,7 +75,7 @@ "ts_omit_synology", "ts_omit_bird" ], - "ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", "trimpath": true } }, @@ -99,7 +108,7 @@ "ts_omit_synology", "ts_omit_bird" ], - "ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", "trimpath": true }, "overrides": [ @@ -112,6 +121,38 @@ "tags_append": ["with_low_memory"] } ] + }, + { + "id": "windows", + "packages": ["libbox"], + "default": { + "tags": [ + "with_gvisor", + "with_quic", + "with_wireguard", + "with_utls", + "with_naive_outbound", + "with_purego", + "with_clash_api", + "with_conntrack", + "badlinkname", + "tfogo_checklinkname0", + "with_tailscale", + "ts_omit_logtail", + "ts_omit_ssh", + "ts_omit_drive", + "ts_omit_taildrop", + "ts_omit_webclient", + "ts_omit_doctor", + "ts_omit_capture", + "ts_omit_kube", + "ts_omit_aws", + "ts_omit_synology", + "ts_omit_bird" + ], + "ldflags": "-X github.com/sagernet/sing-box/constant.Version=${VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0", + "trimpath": true + } } ], "platforms": [ @@ -119,12 +160,19 @@ "type": "android", "build": "android-main", "min_sdk": 23, + "ndk_version": "28.0.13004108", "lib_name": "box", "languages": [{ "type": "java" }], "artifacts": [ { "type": "aar", - "output_path": "libbox.aar" + "output_path": "libbox.aar", + "execute_after": [ + "if [ -d \"${DEPLOY_ANDROID}\" ]; then", + " rm -f \"${DEPLOY_ANDROID}/$$(basename \"${OUTPUT_PATH}\")\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_ANDROID}/\"", + "fi" + ] } ] }, @@ -132,12 +180,19 @@ "type": "android", "build": "android-legacy", "min_sdk": 21, + "ndk_version": "28.0.13004108", "lib_name": "box", "languages": [{ "type": "java" }], "artifacts": [ { "type": "aar", - "output_path": "libbox-legacy.aar" + "output_path": "libbox-legacy.aar", + "execute_after": [ + "if [ -d \"${DEPLOY_ANDROID}\" ]; then", + " rm -f \"${DEPLOY_ANDROID}/$$(basename \"${OUTPUT_PATH}\")\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_ANDROID}/\"", + "fi" + ] } ] }, @@ -159,7 +214,46 @@ { "type": "xcframework", "module_name": "Libbox", - "output_path": "Libbox.xcframework" + "execute_after": [ + "if [ -d \"${DEPLOY_APPLE}\" ]; then", + " rm -rf \"${DEPLOY_APPLE}/${MODULE_NAME}.xcframework\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_APPLE}/\"", + "fi" + ] + } + ] + }, + { + "type": "csharp", + "build": "windows", + "targets": [ + "windows/amd64" + ], + "languages": [{ "type": "csharp" }], + "artifacts": [ + { + "type": "nuget", + "package_id": "SagerNet.Libbox", + "package_version": "0.0.0-local", + "execute_after": { + "windows": [ + "$$deployPath = '${DEPLOY_WINDOWS}'", + "if (Test-Path $$deployPath) {", + " Remove-Item \"$$deployPath\\${PACKAGE_ID}.*.nupkg\" -ErrorAction SilentlyContinue", + " Move-Item -Force '${OUTPUT_PATH}' \"$$deployPath\\\"", + " $$cachePath = if ($$env:NUGET_PACKAGES) { $$env:NUGET_PACKAGES } else { \"$$env:USERPROFILE\\.nuget\\packages\" }", + " Remove-Item -Recurse -Force \"$$cachePath\\sagernet.libbox\\${PACKAGE_VERSION}\" -ErrorAction SilentlyContinue", + "}" + ], + "default": [ + "if [ -d \"${DEPLOY_WINDOWS}\" ]; then", + " rm -f \"${DEPLOY_WINDOWS}/${PACKAGE_ID}.*.nupkg\"", + " mv \"${OUTPUT_PATH}\" \"${DEPLOY_WINDOWS}/\"", + " cache_path=\"$${NUGET_PACKAGES:-$${HOME}/.nuget/packages}\"", + " rm -rf \"$${cache_path}/sagernet.libbox/${PACKAGE_VERSION}\"", + "fi" + ] + } } ] } From 804606042f75e593433db76cc43d31056823bbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Feb 2026 21:12:02 +0800 Subject: [PATCH 138/185] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/clients/android b/clients/android index 6491eff6..2ad11b70 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 6491eff61e0bc71d545d4a620f8a697f2a90f839 +Subproject commit 2ad11b70457919647b3497379b0cc3b1c24ae5ef diff --git a/clients/apple b/clients/apple index 38e8b3ed..76d77804 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 38e8b3eda9f0203dbe63c543f9c5f731ce7961c5 +Subproject commit 76d7780464326564d4629e079a3bc020cf785962 diff --git a/docs/changelog.md b/docs/changelog.md index 616b47af..3986063d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.13.0-rc.3 +#### 1.13.0-rc.4 * Fixes and improvements @@ -169,6 +169,14 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; use NaiveProxy instead for TLS fingerprint resistance. +#### 1.12.22 + +* Fixes and improvements + +#### 1.13.0-rc.3 + +* Fixes and improvements + #### 1.12.21 * Fixes and improvements From de4fdbe5534c41cf70485174659a64e2cb7bc984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 16 Feb 2026 11:28:48 +0800 Subject: [PATCH 139/185] platform: Add semver helper --- experimental/libbox/semver.go | 27 +++++++++++++++++++++++++++ experimental/libbox/semver_test.go | 16 ++++++++++++++++ test/socks_test.go | 1 - 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 experimental/libbox/semver.go create mode 100644 experimental/libbox/semver_test.go diff --git a/experimental/libbox/semver.go b/experimental/libbox/semver.go new file mode 100644 index 00000000..b0919222 --- /dev/null +++ b/experimental/libbox/semver.go @@ -0,0 +1,27 @@ +package libbox + +import ( + "strings" + + "golang.org/x/mod/semver" +) + +func CompareSemver(left string, right string) bool { + normalizedLeft := normalizeSemver(left) + if !semver.IsValid(normalizedLeft) { + return false + } + normalizedRight := normalizeSemver(right) + if !semver.IsValid(normalizedRight) { + return false + } + return semver.Compare(normalizedLeft, normalizedRight) > 0 +} + +func normalizeSemver(version string) string { + trimmedVersion := strings.TrimSpace(version) + if strings.HasPrefix(trimmedVersion, "v") { + return trimmedVersion + } + return "v" + trimmedVersion +} diff --git a/experimental/libbox/semver_test.go b/experimental/libbox/semver_test.go new file mode 100644 index 00000000..f76093b4 --- /dev/null +++ b/experimental/libbox/semver_test.go @@ -0,0 +1,16 @@ +package libbox + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCompareSemver(t *testing.T) { + t.Parallel() + + require.False(t, CompareSemver("1.13.0-rc.4", "1.13.0")) + require.True(t, CompareSemver("1.13.1", "1.13.0")) + require.False(t, CompareSemver("v1.13.0", "1.13.0")) + require.False(t, CompareSemver("1.13.0-", "1.13.0")) +} diff --git a/test/socks_test.go b/test/socks_test.go index 63dca5d6..d33e349c 100644 --- a/test/socks_test.go +++ b/test/socks_test.go @@ -123,7 +123,6 @@ func testUDPSessionIdleTimeout(t *testing.T, proxyPort uint16, echoPort uint16, packetConn.SetReadDeadline(time.Now().Add(3 * time.Second)) n, _, err = packetConn.ReadFrom(buffer) - if err != nil { t.Logf("Read after timeout correctly failed: %v", err) return From d1f1271a02aab6e41674d5c41a53fc3794e1e03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 16 Feb 2026 12:46:29 +0800 Subject: [PATCH 140/185] quic-go: Minor fixes --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8fe1ea40..e32ce488 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 - github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 + github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 github.com/sagernet/sing v0.8.0-beta.16 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.12 diff --git a/go.sum b/go.sum index c428268f..72f283e3 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw= -github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= +github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA= github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= From c0304b83625bbcf343b914d7253d85c2f8f4b0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 16 Feb 2026 12:46:43 +0800 Subject: [PATCH 141/185] Bump version --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 3986063d..ea9735ec 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,7 @@ icon: material/alert-decagram --- -#### 1.13.0-rc.4 +#### 1.13.0-rc.5 * Fixes and improvements From c59991420e79b965b701e3a9e797b256c04eafdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 18 Feb 2026 01:26:29 +0800 Subject: [PATCH 142/185] Minor fixes for naive --- .github/CRONET_GO_VERSION | 2 +- go.mod | 48 ++++++++++---------- go.sum | 96 +++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 9063dff3..b544b75f 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -dc1cda1fe28740ba069934ab62aeb8ef85388332 +71a7ecb25823f2f28620f2f94dc9dd3a5c62c717 diff --git a/go.mod b/go.mod index e32ce488..a70c0352 100644 --- a/go.mod +++ b/go.mod @@ -27,8 +27,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 - github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 + github.com/sagernet/cronet-go v0.0.0-20260217163133-71a7ecb25823 + github.com/sagernet/cronet-go/all v0.0.0-20260217163133-71a7ecb25823 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -105,28 +105,28 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index 72f283e3..699e0a4e 100644 --- a/go.sum +++ b/go.sum @@ -150,54 +150,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 h1:0BYNmr0ptjsII948U0oBFmrbo4qEaCFcrE2JPRg3Zlk= -github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 h1:ghxhYSBQpzkakqWqJDvXr/Zmxe0WjTjKuALEGbjGiGY= -github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:M+4ZjPhLJXIvoxcQsbDofmc19Wrig59hZ+hLvj6S3To= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f h1:8jZbZ4KBTdcXDFLwUBNQt5Xci6ZuAKh255S8TwuBCaM= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f h1:tG0hCx+0u5zca7qQ7AMkcv4DCrBG/DKW1ggs/P+BRRI= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f h1:ZXp5hKJIA7iJ52ZShJCKMQEPLpp/7dDIVZmPGV9Il40= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f h1:gL7H8HS8s38adz4/HZtRHh79qMwsbLTRRPz4GQ9LcWI= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f h1:Dchgc0pAY5Jwb5lzUlE+1nhHIzqLx+YOurXLHgvWd/0= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f h1:+MOLSQoduuKDxF410i1LcSPaQGaiP0eZb0INvMlmjM4= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:lIZna05Vn6n8k21p8OpSUnhwGm+E57PrMjiI4ZUfMSg= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f h1:B2aFQ5CRHI20t8YsEizvtguS5W2QfK7D5XV/NzTIxPE= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:qpSwJ1rFGYCfJDenNCZoWYjoG7N+xEa6ke+E7/JO1i4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f h1:cx7Ipg0tSvTDjS4maMEYz4vuzz93BMPAysmZ1YLrz80= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f h1:4jOHuUiBxD8pJEpBBVQfJqyLmxjpd3t4MLRzU7YLFyg= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f h1:OpXBa2WlRU+Mam9oRe9Nn4/zf7gQ+qiBTNK8A5RwbfQ= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f h1:nJpGFi+6hI85tl4zoyNFEnFEQ5+xEV5gyvsUoMvd8g0= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f h1:SEy2rpmgOJgrqcEryJI/RSnqUWIsEsp0cfYoA8y21jc= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f h1:EW2TuFMLm0iBGqRZtuGwIZdeYmDtDsDmRcRRJQOMxUo= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f h1:3U5woxrNCkzfv1+UX+mVoWh1228AE1qAiMG02F9oFbY= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f h1:YwFTfuWG3mmctroeDYtFZ6LHjGsedVO+5wInYbbUuUY= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:r4V0ddPCRLgGu0VdgR3aUsO9NjpmyjAf+h+3oTD9D6E= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f h1:B8yf4gFvEYUnwWmtVK9sdwUsflYZ387MhYmlOP2ohFQ= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:9YyaMg4rO1/jIgrxmNb0LKH+X7frSYWfX2pFgW5JUVM= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f h1:B0fnGu0sh9yT/9JDN5u/GqThGoOzNN/daOAuGWFLXEk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f h1:lxPcIXKSSI5JDhc7rx/6yufISWM4vtBS2FY9PavWQTs= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20260217163133-71a7ecb25823 h1:9XbfvBCk5ELnJ3eCLwXpJM0CDBba0ZHJOfmFyhNFCow= +github.com/sagernet/cronet-go v0.0.0-20260217163133-71a7ecb25823/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260217163133-71a7ecb25823 h1:WxH8uD7xRmmvGb4lRrr2E7NMCXtti2+pQTnUt61BcmY= +github.com/sagernet/cronet-go/all v0.0.0-20260217163133-71a7ecb25823/go.mod h1:eQ7M+vx5EvWIxwhpOowy2uUIKeJrKKgX0rgsV9RK+ug= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260217162658-2bfd08238a86 h1:jkUKjlIJjUGpYbwhZgjEnAe/IJAtnszMH1T08g+DuaM= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:ypY9a/AfaGhPGVYdoJ/uQOq0oK8BEI5GPrVfwqvyHrg= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260217162658-2bfd08238a86 h1:xue0tra/qcrCtxcSng4WYS4q01Dnsww9u6XSoVJA4sg= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260217162658-2bfd08238a86/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:1RwX26i/6tPMG5/xty16eD7W6gBL1vcOTeZ6hEN6bY4= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:51GT1OcjjWGlTQZJC5GASpOxGHbr8PVs+wJo7znMqjQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:abnUJ2RSsvhfqL2rAwGL9wt9obNQttOOKVRa+d/lzX0= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:qhrdVZgYGaRuwUXKxYWyXxiM3+upyX0vXbVwvfqsPl4= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:SemvxwP5Sqxg2V9kcM/ERlXv7qbiNDJXb7gOBmlJ1o0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:v88k4gE/Nr53Cmx/F3fz+k7izllcnZIg2Y4f7dSkoRE= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260217162658-2bfd08238a86 h1:HpAanCJjJwoVandt54XjU6LhH1sVgO51H9aVh4RAt3c= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260217162658-2bfd08238a86 h1:5LZxdx4X+wG5t8ESxjV41UcxoUM2rS48aprnkY+nTdY= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:Pj/ik7Ux5jWEhUiiavILNwO2k4A4KmijJ2yqDEnxJuY= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260217162658-2bfd08238a86 h1:gDlkdfFVjpWmUfVDXLmRkvfOt7UM9M9CNKJLJWfH4oI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260217162658-2bfd08238a86 h1:5cgey8hMXnc7oSD6h4qJtGfBSPxCXMoq3vsiuD3lgsE= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260217162658-2bfd08238a86/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:qxl5A0k37PBhCZPkswKFN/Ig2PMYCm/9sCFT6e/gRjc= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260217162658-2bfd08238a86 h1:5tKDHiM9rHIpnTm4tIqOrcjWZ9SrU9RTgPLCVy9C1Io= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260217162658-2bfd08238a86 h1:o5hG0iP/xkGx+MkOJwUQp4nkg2Qhz8KIZuON2VcdF4Y= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:ONOmm7KXwv9ArBdw2ozHPcqiiQH5Z2dhE78204iLB5g= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:9kPYonCM0Jly0vDGWeCM5+rwtujgXjkhimTqDGKhVpo= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:7j3jZ260pELBZ9F/3pKPiuVlQhJdCqjA3lRWGtaeT14= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:/2Ks+eLOFXurh5bF3xPbdhQ+rHYkG27RhGORNSDDHjU= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:6YzyCGxKTFBOU9PXW2kYLjfxFTwbv0vwYVDTRcNTe3M= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.11 h1:niMQAspvuThup5eRZQpsGcbM76zAvnsGr7RUIpnQMDQ= From aa85cbb86e99d1edb9a5ed228c71d73067d7d332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 20 Feb 2026 22:05:28 +0800 Subject: [PATCH 143/185] Treat H3 RequestCanceled as closed --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a70c0352..d3cfb02c 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 github.com/sagernet/sing v0.8.0-beta.16 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.12 + github.com/sagernet/sing-quic v0.6.0-beta.13 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index 699e0a4e..f0c2ba70 100644 --- a/go.sum +++ b/go.sum @@ -214,8 +214,8 @@ github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5 github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.12 h1:njyU2NYGBITShAu31wJRmqAtx7hQBcXqBPowDv+W0sk= -github.com/sagernet/sing-quic v0.6.0-beta.12/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= +github.com/sagernet/sing-quic v0.6.0-beta.13 h1:umDr6GC5fVbOIoTvqV4544wY61zEN+ObQwVGNP8sX1M= +github.com/sagernet/sing-quic v0.6.0-beta.13/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= From b5800847aebe58eed71f083bb14faf0a062a3f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Feb 2026 09:40:08 +0800 Subject: [PATCH 144/185] More linux builds for naive --- .github/CRONET_GO_VERSION | 2 +- .github/workflows/build.yml | 35 ++++++++--- .github/workflows/docker.yml | 20 ++++++- .github/workflows/linux.yml | 21 +++++-- go.mod | 55 ++++++++++-------- go.sum | 110 ++++++++++++++++++++--------------- protocol/naive/outbound.go | 4 +- 7 files changed, 157 insertions(+), 90 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index b544b75f..a703eff5 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -71a7ecb25823f2f28620f2f94dc9dd3a5c62c717 +abd78bb191a815236485ad929716845ffb41465a diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42122d03..ba278bbd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,19 +85,27 @@ jobs: - { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" } - { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } + - { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc } + - { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" } + - { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el } + - { os: linux, arch: riscv64, naive: true, variant: glibc } + - { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" } + - { os: linux, arch: loong64, naive: true, variant: glibc } + - { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" } + - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" } - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" } - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" } - { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" } - - { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" } - - { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" } + - { os: linux, arch: mipsle, gomips: hardfloat, openwrt: "mipsel_24kc_24kf" } + - { os: linux, arch: mipsle, gomips: softfloat } - { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" } - - { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el } + - { os: linux, arch: mips64le, gomips: hardfloat } - { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" } - { os: linux, arch: s390x, debian: s390x, rpm: s390x } - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } - - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" } - - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" } + - { os: linux, arch: riscv64 } + - { os: linux, arch: loong64 } - { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" } - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" } @@ -154,14 +162,23 @@ jobs: git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" git -C ~/cronet-go checkout FETCH_HEAD git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Regenerate Debian keyring + if: matrix.naive + run: | + set -xeuo pipefail + rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg + cd ~/cronet-go + GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh - name: Cache Chromium toolchain if: matrix.naive id: cache-chromium-toolchain uses: actions/cache@v4 with: path: | - ~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts - ~/cronet-go/naiveproxy/src/out/sysroot-build + ~/cronet-go/naiveproxy/src/third_party/llvm-build/ + ~/cronet-go/naiveproxy/src/gn/out/ + ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/ + ~/cronet-go/naiveproxy/src/out/sysroot-build/ key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }} - name: Download Chromium toolchain if: matrix.naive @@ -236,6 +253,8 @@ jobs: GOARCH: ${{ matrix.arch }} GO386: ${{ matrix.go386 }} GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build (musl) if: matrix.variant == 'musl' @@ -251,6 +270,8 @@ jobs: GOARCH: ${{ matrix.arch }} GO386: ${{ matrix.go386 }} GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build (non-variant) if: matrix.os != 'android' && matrix.variant == '' diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e5ec3a20..ed69c5df 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -29,10 +29,12 @@ jobs: - { arch: arm64, naive: true, docker_platform: "linux/arm64" } - { arch: "386", naive: true, docker_platform: "linux/386" } - { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" } + - { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" } + - { arch: riscv64, naive: true, docker_platform: "linux/riscv64" } + - { arch: loong64, naive: true, docker_platform: "linux/loong64" } # Non-naive builds - { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" } - { arch: ppc64le, docker_platform: "linux/ppc64le" } - - { arch: riscv64, docker_platform: "linux/riscv64" } - { arch: s390x, docker_platform: "linux/s390x" } steps: - name: Get commit to build @@ -64,14 +66,23 @@ jobs: git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" git -C ~/cronet-go checkout FETCH_HEAD git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Regenerate Debian keyring + if: matrix.naive + run: | + set -xeuo pipefail + rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg + cd ~/cronet-go + GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh - name: Cache Chromium toolchain if: matrix.naive id: cache-chromium-toolchain uses: actions/cache@v4 with: path: | - ~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts - ~/cronet-go/naiveproxy/src/out/sysroot-build + ~/cronet-go/naiveproxy/src/third_party/llvm-build/ + ~/cronet-go/naiveproxy/src/gn/out/ + ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/ + ~/cronet-go/naiveproxy/src/out/sysroot-build/ key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }} - name: Download Chromium toolchain if: matrix.naive @@ -110,6 +121,7 @@ jobs: GOOS: linux GOARCH: ${{ matrix.arch }} GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} - name: Build (non-naive) if: ${{ ! matrix.naive }} run: | @@ -154,9 +166,11 @@ jobs: - linux/arm/v7 - linux/arm64 - linux/386 + - linux/mipsle - linux/ppc64le - linux/riscv64 - linux/s390x + - linux/loong64 steps: - name: Get commit to build id: ref diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1311788e..79e68683 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -61,14 +61,14 @@ jobs: - { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 } - { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 } - { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl } + - { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel } + - { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 } + - { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 } # Non-naive builds (unsupported architectures) - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl } - { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el } - - { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel } - { os: linux, arch: s390x, debian: s390x, rpm: s390x } - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } - - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 } - - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 } steps: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 @@ -88,14 +88,23 @@ jobs: git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" git -C ~/cronet-go checkout FETCH_HEAD git -C ~/cronet-go submodule update --init --recursive --depth=1 + - name: Regenerate Debian keyring + if: matrix.naive + run: | + set -xeuo pipefail + rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg + cd ~/cronet-go + GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh - name: Cache Chromium toolchain if: matrix.naive id: cache-chromium-toolchain uses: actions/cache@v4 with: path: | - ~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts - ~/cronet-go/naiveproxy/src/out/sysroot-build + ~/cronet-go/naiveproxy/src/third_party/llvm-build/ + ~/cronet-go/naiveproxy/src/gn/out/ + ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/ + ~/cronet-go/naiveproxy/src/out/sysroot-build/ key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }} - name: Download Chromium toolchain if: matrix.naive @@ -134,6 +143,8 @@ jobs: GOOS: linux GOARCH: ${{ matrix.arch }} GOARM: ${{ matrix.goarm }} + GOMIPS: ${{ matrix.gomips }} + GOMIPS64: ${{ matrix.gomips }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build (non-naive) if: ${{ ! matrix.naive }} diff --git a/go.mod b/go.mod index d3cfb02c..797294f6 100644 --- a/go.mod +++ b/go.mod @@ -27,8 +27,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20260217163133-71a7ecb25823 - github.com/sagernet/cronet-go/all v0.0.0-20260217163133-71a7ecb25823 + github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8 + github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -105,28 +105,35 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260217162658-2bfd08238a86 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260217162658-2bfd08238a86 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index f0c2ba70..cf2ae638 100644 --- a/go.sum +++ b/go.sum @@ -150,54 +150,68 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20260217163133-71a7ecb25823 h1:9XbfvBCk5ELnJ3eCLwXpJM0CDBba0ZHJOfmFyhNFCow= -github.com/sagernet/cronet-go v0.0.0-20260217163133-71a7ecb25823/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20260217163133-71a7ecb25823 h1:WxH8uD7xRmmvGb4lRrr2E7NMCXtti2+pQTnUt61BcmY= -github.com/sagernet/cronet-go/all v0.0.0-20260217163133-71a7ecb25823/go.mod h1:eQ7M+vx5EvWIxwhpOowy2uUIKeJrKKgX0rgsV9RK+ug= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260217162658-2bfd08238a86 h1:jkUKjlIJjUGpYbwhZgjEnAe/IJAtnszMH1T08g+DuaM= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:ypY9a/AfaGhPGVYdoJ/uQOq0oK8BEI5GPrVfwqvyHrg= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260217162658-2bfd08238a86 h1:xue0tra/qcrCtxcSng4WYS4q01Dnsww9u6XSoVJA4sg= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260217162658-2bfd08238a86/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:1RwX26i/6tPMG5/xty16eD7W6gBL1vcOTeZ6hEN6bY4= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:51GT1OcjjWGlTQZJC5GASpOxGHbr8PVs+wJo7znMqjQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:abnUJ2RSsvhfqL2rAwGL9wt9obNQttOOKVRa+d/lzX0= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:qhrdVZgYGaRuwUXKxYWyXxiM3+upyX0vXbVwvfqsPl4= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:SemvxwP5Sqxg2V9kcM/ERlXv7qbiNDJXb7gOBmlJ1o0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:v88k4gE/Nr53Cmx/F3fz+k7izllcnZIg2Y4f7dSkoRE= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260217162658-2bfd08238a86 h1:HpAanCJjJwoVandt54XjU6LhH1sVgO51H9aVh4RAt3c= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260217162658-2bfd08238a86 h1:5LZxdx4X+wG5t8ESxjV41UcxoUM2rS48aprnkY+nTdY= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:Pj/ik7Ux5jWEhUiiavILNwO2k4A4KmijJ2yqDEnxJuY= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260217162658-2bfd08238a86 h1:gDlkdfFVjpWmUfVDXLmRkvfOt7UM9M9CNKJLJWfH4oI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260217162658-2bfd08238a86 h1:5cgey8hMXnc7oSD6h4qJtGfBSPxCXMoq3vsiuD3lgsE= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260217162658-2bfd08238a86/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:qxl5A0k37PBhCZPkswKFN/Ig2PMYCm/9sCFT6e/gRjc= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260217162658-2bfd08238a86 h1:5tKDHiM9rHIpnTm4tIqOrcjWZ9SrU9RTgPLCVy9C1Io= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260217162658-2bfd08238a86 h1:o5hG0iP/xkGx+MkOJwUQp4nkg2Qhz8KIZuON2VcdF4Y= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260217162658-2bfd08238a86/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:ONOmm7KXwv9ArBdw2ozHPcqiiQH5Z2dhE78204iLB5g= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:9kPYonCM0Jly0vDGWeCM5+rwtujgXjkhimTqDGKhVpo= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260217162658-2bfd08238a86 h1:7j3jZ260pELBZ9F/3pKPiuVlQhJdCqjA3lRWGtaeT14= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260217162658-2bfd08238a86/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260217162658-2bfd08238a86 h1:/2Ks+eLOFXurh5bF3xPbdhQ+rHYkG27RhGORNSDDHjU= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260217162658-2bfd08238a86 h1:6YzyCGxKTFBOU9PXW2kYLjfxFTwbv0vwYVDTRcNTe3M= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260217162658-2bfd08238a86/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8 h1:XcZiLUXnYE74RvqVdsyxgIInBuFaZbABx2Hom5U6uuk= +github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8 h1:uaUy9opPmPYD+viUeUnBzT+lw5b19j6pC/iKew7u13I= +github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8/go.mod h1:Gn1d0D8adjp7mlgSv+/pVLJsG+engIMBp/R4+1MOhlk= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe h1:iKIZJsvD+D3sdAzAeeOodJBxnFL9OVs1LTq3xnmQ6wQ= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:/YhWKKVb3uQ5JmBQwFEOKg8QK2w0Ky6dxEb/UHrhQww= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe h1:h+XF746wRtYKavUeS8//Vro6s9f0F6+pI8VQFLMLg6E= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:yMs96D9ErwAG8gEHV6zaQ5cp9ZPNBHExxJ5+u8cZ644= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:HUJtGjXcB+70W+YfeLgue6X1u69XLN0Ar56Ipg3gtvY= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:ivo7JwVqDTMf/qVfpKYdwcIc+NzKGyMJ/WLj/TTNYXg= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:HdWJLwa/Ie3jsueJ0O2mZd4V/NP1UJ6bamdcNHWsYEo= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:A9PWi2xCI+TCr9ALr+BO76WCCk1JnRyjeEH0/+rdyRc= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:aMOUWbGjkPBFqObA+uAJOfVuBkHfvz2sibNgOJqjuBs= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe h1:CzE+sJ2iOvJwOuZhpiDV5VlQrBaNJAZhDCafly+TH9c= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe h1:2grC2CeyUiYVgqG7BGKpJvjFzYv0wL64QMoBqOHVZsI= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:+N9/LauocInR5kxXU+L5bQe1bndCZUC+6L0FaozWZNI= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe h1:mRJcjGtKG/eaPL4sZ4Ij+e7aLdg1AEXNI1PgRnxI6H8= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe h1:UkWiTAxUAjTtsu7e52cvMrmbShz+ahTdGkhF9mEIIZU= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:giJVex0bwZy+DwmPwfZ+NZmafBRTsaZ+QUaD2Fkacxs= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe h1:XkjAQkciY78eSMF/9VdaWRWb+OfPvoIxVKx5gHGfSIg= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe h1:8uDfbPXAL0MWqGI8bm6YJghRmGvK08z4jEIGoODKqTI= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe h1:Ucs4htbATTdG7YGHCyQ4vMYRhltVvsapZ95THRNssr4= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe h1:oeQjTH4lveV4M7/hqOJFfwQ9UfWvkFZEXTc00R2acuk= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe h1:NWABhpSuXcN61hF0CUqwliJXxEbmHidoAHxtB61V3GA= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe h1:+0VrQdlGR/zLjPzinXFqFR2sdzF2BXoXu7f8xaMuwtg= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe h1:DYW55QJOZBI4Znjhc0IiusF+IMg4R2dHPX0KnZC6gSo= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe h1:xbbZtyXOxYJMplsyv371ddQb7QrEnyXIIGdUK/3WNTE= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe h1:YSH2lVT+Sn29lQQbwhDpxZvGjVSg80SUfW4JQ8vM3aA= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:gQ1veofYJr8Z1hBVM2PIrn4+EMKvwh+zWpYBr+mxgQ8= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:e2TMlbEottRCDfTWxUSw4Jl5dK8IInV02XIvLKVjLbM= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:Cgh+DP/Ns1djisz+LFxA1nEhyF6EEU5ZdVxNTkiX2BI= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:VCtjRmkI1IkKdWQ3Jh7j/ze5fhBQJZo1JR70cVKLaKw= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:SKePXZMEPUY5zA1VFBPbPOxZsfb/wkMNZAvjPO7hL+I= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.11 h1:niMQAspvuThup5eRZQpsGcbM76zAvnsGr7RUIpnQMDQ= diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index b2a43028..dcc1aec5 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -235,7 +235,7 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) - return h.client.DialEarly(destination) + return h.client.DialEarly(ctx, destination) case N.NetworkUDP: if h.uotClient == nil { return nil, E.New("UDP is not supported unless UDP over TCP is enabled") @@ -267,5 +267,5 @@ type naiveDialer struct { } func (d *naiveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return d.NaiveClient.DialEarly(destination) + return d.NaiveClient.DialEarly(ctx, destination) } From 6a95c66bc78cf1170dcecd0ddb94658f7d7eb64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Feb 2026 13:50:51 +0800 Subject: [PATCH 145/185] Pin Go version to 1.25.7 --- .github/workflows/build.yml | 10 +++++----- .github/workflows/docker.yml | 2 +- .github/workflows/linux.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba278bbd..bbd61b85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.7 + go-version: ~1.25.7 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -123,7 +123,7 @@ jobs: if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }} uses: actions/setup-go@v5 with: - go-version: ^1.25.7 + go-version: ~1.25.7 - name: Setup Go 1.24 if: matrix.legacy_go124 uses: actions/setup-go@v5 @@ -592,7 +592,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.7 + go-version: ~1.25.7 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -682,7 +682,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.7 + go-version: ~1.25.7 - name: Setup Android NDK id: setup-ndk uses: nttld/setup-ndk@v1 @@ -781,7 +781,7 @@ jobs: if: matrix.if uses: actions/setup-go@v5 with: - go-version: ^1.25.7 + go-version: ~1.25.7 - name: Set tag if: matrix.if run: |- diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ed69c5df..fd8845eb 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -55,7 +55,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.4 + go-version: ~1.25.7 - name: Clone cronet-go if: matrix.naive run: | diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 79e68683..414d02e5 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.7 + go-version: ~1.25.7 - name: Check input version if: github.event_name == 'workflow_dispatch' run: |- @@ -77,7 +77,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ^1.25.7 + go-version: ~1.25.7 - name: Clone cronet-go if: matrix.naive run: | From 9bcd715d3106665e741659f81880f18d7e38821a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 21 Feb 2026 12:36:30 +0800 Subject: [PATCH 146/185] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/android b/clients/android index 2ad11b70..d3e6add3 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 2ad11b70457919647b3497379b0cc3b1c24ae5ef +Subproject commit d3e6add3e027624886ff7a9b7ce0b07902c86ab2 diff --git a/clients/apple b/clients/apple index 76d77804..015dcd26 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 76d7780464326564d4629e079a3bc020cf785962 +Subproject commit 015dcd266ba8651f5be20de531bf9184470f750d diff --git a/docs/changelog.md b/docs/changelog.md index ea9735ec..b0c53e6f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,7 +4,7 @@ icon: material/alert-decagram #### 1.13.0-rc.5 -* Fixes and improvements +* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound Important changes since 1.12: From 7745a97cca2ca7c37166e23b0177e5817c917f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 22 Feb 2026 23:27:25 +0800 Subject: [PATCH 147/185] daemon: Fix started service leak --- daemon/started_service.go | 8 ++++++++ experimental/libbox/command_server.go | 1 + 2 files changed, 9 insertions(+) diff --git a/daemon/started_service.go b/daemon/started_service.go index a42a752d..7c6fa945 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -207,6 +207,14 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov return nil } +func (s *StartedService) Close() { + s.serviceStatusSubscriber.Close() + s.logSubscriber.Close() + s.urlTestSubscriber.Close() + s.clashModeSubscriber.Close() + s.connectionEventSubscriber.Close() +} + func (s *StartedService) CloseService() error { s.serviceAccess.Lock() switch s.serviceStatus.Status { diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 2b4ac71b..e3300281 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -159,6 +159,7 @@ func (s *CommandServer) Close() { s.grpcServer.Stop() } common.Close(s.listener) + s.StartedService.Close() } type OverrideOptions struct { From 0817c25f4c661fd3087fc96cebec1d856af4387c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Feb 2026 14:32:32 +0800 Subject: [PATCH 148/185] release: Fix Docker build for loong64 and mipsle --- .github/workflows/docker.yml | 25 ++++++++++++++----------- Dockerfile.binary | 10 ++++++++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fd8845eb..0cb256fd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -160,17 +160,17 @@ jobs: strategy: fail-fast: true matrix: - platform: - - linux/amd64 - - linux/arm/v6 - - linux/arm/v7 - - linux/arm64 - - linux/386 - - linux/mipsle - - linux/ppc64le - - linux/riscv64 - - linux/s390x - - linux/loong64 + include: + - { platform: "linux/amd64" } + - { platform: "linux/arm/v6" } + - { platform: "linux/arm/v7" } + - { platform: "linux/arm64" } + - { platform: "linux/386" } + # mipsle: no base Docker image available for this platform + - { platform: "linux/ppc64le" } + - { platform: "linux/riscv64" } + - { platform: "linux/s390x" } + - { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" } steps: - name: Get commit to build id: ref @@ -223,6 +223,8 @@ jobs: platforms: ${{ matrix.platform }} context: . file: Dockerfile.binary + build-args: | + BASE_IMAGE=${{ matrix.base_image || 'alpine' }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true - name: Export digest @@ -238,6 +240,7 @@ jobs: if-no-files-found: error retention-days: 1 merge: + if: github.event_name != 'push' runs-on: ubuntu-latest needs: - build_docker diff --git a/Dockerfile.binary b/Dockerfile.binary index ac46e53a..78fc5667 100644 --- a/Dockerfile.binary +++ b/Dockerfile.binary @@ -1,8 +1,14 @@ -FROM alpine +ARG BASE_IMAGE=alpine +FROM ${BASE_IMAGE} ARG TARGETARCH ARG TARGETVARIANT LABEL maintainer="nekohasekai " RUN set -ex \ - && apk add --no-cache --upgrade bash tzdata ca-certificates nftables + && if command -v apk > /dev/null; then \ + apk add --no-cache --upgrade bash tzdata ca-certificates nftables; \ + else \ + apt-get update && apt-get install -y --no-install-recommends bash tzdata ca-certificates nftables \ + && rm -rf /var/lib/apt/lists/*; \ + fi COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box ENTRYPOINT ["sing-box"] From e0c18cc3d4e748f952403b3d2313dc241e179bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Feb 2026 17:13:36 +0800 Subject: [PATCH 149/185] tun: Fix nftablesCreateLocalAddressSets --- go.mod | 2 +- go.sum | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 797294f6..ad9d4f06 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.17 + github.com/sagernet/sing-tun v0.8.0-beta.18 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 diff --git a/go.sum b/go.sum index cf2ae638..7d952142 100644 --- a/go.sum +++ b/go.sum @@ -236,8 +236,10 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.17 h1:6DdbNXeTFYj8Tb4FCh8Mp2boA3rVY6VNqzTOObj7Xis= -github.com/sagernet/sing-tun v0.8.0-beta.17/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f h1:3B8auqVameLvPhWxzw5S8QWdLTv19o0M8LYxPOXNm0g= +github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.0-beta.18 h1:C6oHxP9BNBVEVdC9ABMTXmKej9mUVtcuw2v+IiBS8yw= +github.com/sagernet/sing-tun v0.8.0-beta.18/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From 94ed42caf1677293d249f9fb8c82c5284fddd0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 23 Feb 2026 16:38:30 +0800 Subject: [PATCH 150/185] Bump version --- clients/android | 2 +- docs/changelog.md | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/clients/android b/clients/android index d3e6add3..4bdde0ae 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit d3e6add3e027624886ff7a9b7ce0b07902c86ab2 +Subproject commit 4bdde0ae4db2f0fe041a52520e12315141d0981c diff --git a/docs/changelog.md b/docs/changelog.md index b0c53e6f..d3162824 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,9 +2,9 @@ icon: material/alert-decagram --- -#### 1.13.0-rc.5 +#### 1.13.0-rc.6 -* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound +* Fixes and improvements Important changes since 1.12: @@ -169,6 +169,10 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; use NaiveProxy instead for TLS fingerprint resistance. +#### 1.13.0-rc.5 + +* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound + #### 1.12.22 * Fixes and improvements From 4c05d7b888845f7a96f32332ba3a7803e2710fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 24 Feb 2026 15:31:57 +0800 Subject: [PATCH 151/185] Add advertise tags support for Tailscale endpoint --- docs/configuration/endpoint/tailscale.md | 12 +++++++- docs/configuration/endpoint/tailscale.zh.md | 12 +++++++- option/tailscale.go | 33 +++++++++++---------- protocol/tailscale/endpoint.go | 20 +++++++------ 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/docs/configuration/endpoint/tailscale.md b/docs/configuration/endpoint/tailscale.md index 5ea89a0e..6cf10e2b 100644 --- a/docs/configuration/endpoint/tailscale.md +++ b/docs/configuration/endpoint/tailscale.md @@ -8,7 +8,8 @@ icon: material/new-box :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) :material-plus: [system_interface](#system_interface) :material-plus: [system_interface_name](#system_interface_name) - :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [advertise_tags](#advertise_tags) !!! question "Since sing-box 1.12.0" @@ -28,6 +29,7 @@ icon: material/new-box "exit_node_allow_lan_access": false, "advertise_routes": [], "advertise_exit_node": false, + "advertise_tags": [], "relay_server_port": 0, "relay_server_static_endpoints": [], "system_interface": false, @@ -102,6 +104,14 @@ Example: `["192.168.1.1/24"]` Indicates whether the node should advertise itself as an exit node. +#### advertise_tags + +!!! question "Since sing-box 1.13.0" + +Tags to advertise for this node, for ACL enforcement purposes. + +Example: `["tag:server"]` + #### relay_server_port !!! question "Since sing-box 1.13.0" diff --git a/docs/configuration/endpoint/tailscale.zh.md b/docs/configuration/endpoint/tailscale.zh.md index 1bd65878..f881dd67 100644 --- a/docs/configuration/endpoint/tailscale.zh.md +++ b/docs/configuration/endpoint/tailscale.zh.md @@ -8,7 +8,8 @@ icon: material/new-box :material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints) :material-plus: [system_interface](#system_interface) :material-plus: [system_interface_name](#system_interface_name) - :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [system_interface_mtu](#system_interface_mtu) + :material-plus: [advertise_tags](#advertise_tags) !!! question "自 sing-box 1.12.0 起" @@ -28,6 +29,7 @@ icon: material/new-box "exit_node_allow_lan_access": false, "advertise_routes": [], "advertise_exit_node": false, + "advertise_tags": [], "relay_server_port": 0, "relay_server_static_endpoints": [], "system_interface": false, @@ -101,6 +103,14 @@ icon: material/new-box 指示节点是否应将自己通告为出口节点。 +#### advertise_tags + +!!! question "自 sing-box 1.13.0 起" + +为此节点通告的标签,用于 ACL 执行。 + +示例:`["tag:server"]` + #### relay_server_port !!! question "自 sing-box 1.13.0 起" diff --git a/option/tailscale.go b/option/tailscale.go index 661d91a3..dac8e866 100644 --- a/option/tailscale.go +++ b/option/tailscale.go @@ -12,22 +12,23 @@ import ( type TailscaleEndpointOptions struct { DialerOptions - StateDirectory string `json:"state_directory,omitempty"` - AuthKey string `json:"auth_key,omitempty"` - ControlURL string `json:"control_url,omitempty"` - Ephemeral bool `json:"ephemeral,omitempty"` - Hostname string `json:"hostname,omitempty"` - AcceptRoutes bool `json:"accept_routes,omitempty"` - ExitNode string `json:"exit_node,omitempty"` - ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"` - AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"` - AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` - RelayServerPort *uint16 `json:"relay_server_port,omitempty"` - RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"` - SystemInterface bool `json:"system_interface,omitempty"` - SystemInterfaceName string `json:"system_interface_name,omitempty"` - SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + StateDirectory string `json:"state_directory,omitempty"` + AuthKey string `json:"auth_key,omitempty"` + ControlURL string `json:"control_url,omitempty"` + Ephemeral bool `json:"ephemeral,omitempty"` + Hostname string `json:"hostname,omitempty"` + AcceptRoutes bool `json:"accept_routes,omitempty"` + ExitNode string `json:"exit_node,omitempty"` + ExitNodeAllowLANAccess bool `json:"exit_node_allow_lan_access,omitempty"` + AdvertiseRoutes []netip.Prefix `json:"advertise_routes,omitempty"` + AdvertiseExitNode bool `json:"advertise_exit_node,omitempty"` + AdvertiseTags badoption.Listable[string] `json:"advertise_tags,omitempty"` + RelayServerPort *uint16 `json:"relay_server_port,omitempty"` + RelayServerStaticEndpoints []netip.AddrPort `json:"relay_server_static_endpoints,omitempty"` + SystemInterface bool `json:"system_interface,omitempty"` + SystemInterfaceName string `json:"system_interface_name,omitempty"` + SystemInterfaceMTU uint32 `json:"system_interface_mtu,omitempty"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` } type TailscaleDNSServerOptions struct { diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 1bd63e71..40bc4bc6 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -97,6 +97,7 @@ type Endpoint struct { exitNodeAllowLANAccess bool advertiseRoutes []netip.Prefix advertiseExitNode bool + advertiseTags []string relayServerPort *uint16 relayServerStaticEndpoints []netip.AddrPort @@ -244,6 +245,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, advertiseRoutes: options.AdvertiseRoutes, advertiseExitNode: options.AdvertiseExitNode, + advertiseTags: options.AdvertiseTags, relayServerPort: options.RelayServerPort, relayServerStaticEndpoints: options.RelayServerStaticEndpoints, udpTimeout: udpTimeout, @@ -359,25 +361,25 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { localBackend := t.server.ExportLocalBackend() perfs := &ipn.MaskedPrefs{ Prefs: ipn.Prefs{ - RouteAll: t.acceptRoutes, + RouteAll: t.acceptRoutes, + AdvertiseRoutes: t.advertiseRoutes, + AdvertiseTags: t.advertiseTags, }, - RouteAllSet: true, - ExitNodeIPSet: true, - AdvertiseRoutesSet: true, - } - if len(t.advertiseRoutes) > 0 { - perfs.AdvertiseRoutes = t.advertiseRoutes + RouteAllSet: true, + ExitNodeIPSet: true, + AdvertiseRoutesSet: true, + AdvertiseTagsSet: true, + RelayServerPortSet: true, + RelayServerStaticEndpointsSet: true, } if t.advertiseExitNode { perfs.AdvertiseRoutes = append(perfs.AdvertiseRoutes, tsaddr.ExitRoutes()...) } if t.relayServerPort != nil { perfs.RelayServerPort = t.relayServerPort - perfs.RelayServerPortSet = true } if len(t.relayServerStaticEndpoints) > 0 { perfs.RelayServerStaticEndpoints = t.relayServerStaticEndpoints - perfs.RelayServerStaticEndpointsSet = true } _, err = localBackend.EditPrefs(perfs) if err != nil { From d48236da948f6e7def1cb9740bc158bf362e9e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 24 Feb 2026 15:49:46 +0800 Subject: [PATCH 152/185] Fix wireguard reserved --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index ad9d4f06..db97d327 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 - github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 7d952142..bb12cda4 100644 --- a/go.sum +++ b/go.sum @@ -236,8 +236,6 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f h1:3B8auqVameLvPhWxzw5S8QWdLTv19o0M8LYxPOXNm0g= -github.com/sagernet/sing-tun v0.8.0-beta.17.0.20260223095246-5715a3919a7f/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-tun v0.8.0-beta.18 h1:C6oHxP9BNBVEVdC9ABMTXmKej9mUVtcuw2v+IiBS8yw= github.com/sagernet/sing-tun v0.8.0-beta.18/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= @@ -246,8 +244,8 @@ github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1h github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8= github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= -github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA= -github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:f9cXNB+IOOPnR8DOLMTpr42jf7naxh5Un5Y09BBf5Cg= +github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= From 0bc66e5a566a734d90dbd85a24e081b98a075704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 24 Feb 2026 19:16:40 +0800 Subject: [PATCH 153/185] service/ccm,ocm: Fixes and improvements --- go.mod | 4 +- go.sum | 12 +- service/ccm/service.go | 45 +- service/ccm/service_usage.go | 559 +++++++++++++++++-------- service/ocm/service.go | 91 +++- service/ocm/service_usage.go | 777 ++++++++++++++++++++++++++++++----- 6 files changed, 1201 insertions(+), 287 deletions(-) diff --git a/go.mod b/go.mod index db97d327..0523b73f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sagernet/sing-box go 1.24.7 require ( - github.com/anthropics/anthropic-sdk-go v1.19.0 + github.com/anthropics/anthropic-sdk-go v1.26.0 github.com/anytls/sing-anytls v0.0.11 github.com/caddyserver/certmagic v0.25.0 github.com/coder/websocket v1.8.14 @@ -22,7 +22,7 @@ require ( github.com/metacubex/utls v1.8.4 github.com/mholt/acmez/v3 v3.1.4 github.com/miekg/dns v1.1.69 - github.com/openai/openai-go/v3 v3.15.0 + github.com/openai/openai-go/v3 v3.23.0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a diff --git a/go.sum b/go.sum index bb12cda4..738a415b 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE= -github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY= +github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic= @@ -38,6 +38,8 @@ github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbww github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE= @@ -126,8 +128,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo= -github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= +github.com/openai/openai-go/v3 v3.23.0 h1:FRFwTcB4FoWFtIunTY/8fgHvzSHgqbfWjiCwOMVrsvw= +github.com/openai/openai-go/v3 v3.23.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -378,6 +380,8 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/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/service/ccm/service.go b/service/ccm/service.go index 94e47734..ba428060 100644 --- a/service/ccm/service.go +++ b/service/ccm/service.go @@ -10,6 +10,7 @@ import ( "mime" "net" "net/http" + "strconv" "strings" "sync" "time" @@ -79,6 +80,35 @@ func isHopByHopHeader(header string) bool { } } +const ( + weeklyWindowSeconds = 604800 + weeklyWindowMinutes = weeklyWindowSeconds / 60 +) + +func parseInt64Header(headers http.Header, headerName string) (int64, bool) { + headerValue := strings.TrimSpace(headers.Get(headerName)) + if headerValue == "" { + return 0, false + } + parsedValue, parseError := strconv.ParseInt(headerValue, 10, 64) + if parseError != nil { + return 0, false + } + return parsedValue, true +} + +func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint { + resetAtUnix, hasResetAt := parseInt64Header(headers, "anthropic-ratelimit-unified-7d-reset") + if !hasResetAt || resetAtUnix <= 0 { + return nil + } + + return &WeeklyCycleHint{ + WindowMinutes: weeklyWindowMinutes, + ResetAt: time.Unix(resetAtUnix, 0).UTC(), + } +} + type Service struct { boxService.Adapter ctx context.Context @@ -392,6 +422,7 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, requestModel string, anthropicBetaHeader string, messagesCount int, username string) { + weeklyCycleHint := extractWeeklyCycleHint(response.Header) mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) isStreaming := err == nil && mediaType == "text/event-stream" @@ -417,7 +448,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if usage.InputTokens > 0 || usage.OutputTokens > 0 { if responseModel != "" { contextWindow := detectContextWindow(anthropicBetaHeader, usage.InputTokens) - s.usageTracker.AddUsage( + s.usageTracker.AddUsageWithCycleHint( responseModel, contextWindow, messagesCount, @@ -425,7 +456,11 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons usage.OutputTokens, usage.CacheReadInputTokens, usage.CacheCreationInputTokens, + usage.CacheCreation.Ephemeral5mInputTokens, + usage.CacheCreation.Ephemeral1hInputTokens, username, + time.Now(), + weeklyCycleHint, ) } } @@ -485,6 +520,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons accumulatedUsage.InputTokens = messageStart.Message.Usage.InputTokens accumulatedUsage.CacheReadInputTokens = messageStart.Message.Usage.CacheReadInputTokens accumulatedUsage.CacheCreationInputTokens = messageStart.Message.Usage.CacheCreationInputTokens + accumulatedUsage.CacheCreation.Ephemeral5mInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral5mInputTokens + accumulatedUsage.CacheCreation.Ephemeral1hInputTokens = messageStart.Message.Usage.CacheCreation.Ephemeral1hInputTokens } case "message_delta": messageDelta := event.AsMessageDelta() @@ -511,7 +548,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if accumulatedUsage.InputTokens > 0 || accumulatedUsage.OutputTokens > 0 { if responseModel != "" { contextWindow := detectContextWindow(anthropicBetaHeader, accumulatedUsage.InputTokens) - s.usageTracker.AddUsage( + s.usageTracker.AddUsageWithCycleHint( responseModel, contextWindow, messagesCount, @@ -519,7 +556,11 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons accumulatedUsage.OutputTokens, accumulatedUsage.CacheReadInputTokens, accumulatedUsage.CacheCreationInputTokens, + accumulatedUsage.CacheCreation.Ephemeral5mInputTokens, + accumulatedUsage.CacheCreation.Ephemeral1hInputTokens, username, + time.Now(), + weeklyCycleHint, ) } } diff --git a/service/ccm/service_usage.go b/service/ccm/service_usage.go index 7d39e3ce..7d776774 100644 --- a/service/ccm/service_usage.go +++ b/service/ccm/service_usage.go @@ -2,6 +2,7 @@ package ccm import ( "encoding/json" + "fmt" "math" "os" "regexp" @@ -13,17 +14,20 @@ import ( ) type UsageStats struct { - RequestCount int `json:"request_count"` - MessagesCount int `json:"messages_count"` - InputTokens int64 `json:"input_tokens"` - OutputTokens int64 `json:"output_tokens"` - CacheReadInputTokens int64 `json:"cache_read_input_tokens"` - CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"` + CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"` } type CostCombination struct { Model string `json:"model"` ContextWindow int `json:"context_window"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` Total UsageStats `json:"total"` ByUser map[string]UsageStats `json:"by_user"` } @@ -41,18 +45,21 @@ type AggregatedUsage struct { } type UsageStatsJSON struct { - RequestCount int `json:"request_count"` - MessagesCount int `json:"messages_count"` - InputTokens int64 `json:"input_tokens"` - OutputTokens int64 `json:"output_tokens"` - CacheReadInputTokens int64 `json:"cache_read_input_tokens"` - CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` - CostUSD float64 `json:"cost_usd"` + RequestCount int `json:"request_count"` + MessagesCount int `json:"messages_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheReadInputTokens int64 `json:"cache_read_input_tokens"` + CacheCreationInputTokens int64 `json:"cache_creation_input_tokens"` + CacheCreation5MinuteInputTokens int64 `json:"cache_creation_5m_input_tokens,omitempty"` + CacheCreation1HourInputTokens int64 `json:"cache_creation_1h_input_tokens,omitempty"` + CostUSD float64 `json:"cost_usd"` } type CostCombinationJSON struct { Model string `json:"model"` ContextWindow int `json:"context_window"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` Total UsageStatsJSON `json:"total"` ByUser map[string]UsageStatsJSON `json:"by_user"` } @@ -60,6 +67,7 @@ type CostCombinationJSON struct { type CostsSummaryJSON struct { TotalUSD float64 `json:"total_usd"` ByUser map[string]float64 `json:"by_user"` + ByWeek map[string]float64 `json:"by_week,omitempty"` } type AggregatedUsageJSON struct { @@ -68,11 +76,17 @@ type AggregatedUsageJSON struct { Combinations []CostCombinationJSON `json:"combinations"` } +type WeeklyCycleHint struct { + WindowMinutes int64 + ResetAt time.Time +} + type ModelPricing struct { - InputPrice float64 - OutputPrice float64 - CacheReadPrice float64 - CacheWritePrice float64 + InputPrice float64 + OutputPrice float64 + CacheReadPrice float64 + CacheWritePrice5Minute float64 + CacheWritePrice1Hour float64 } type modelFamily struct { @@ -82,143 +96,205 @@ type modelFamily struct { } var ( - opus4Pricing = ModelPricing{ - InputPrice: 15.0, - OutputPrice: 75.0, - CacheReadPrice: 1.5, - CacheWritePrice: 18.75, + opus46StandardPricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice5Minute: 6.25, + CacheWritePrice1Hour: 10.0, } - sonnet4StandardPricing = ModelPricing{ - InputPrice: 3.0, - OutputPrice: 15.0, - CacheReadPrice: 0.3, - CacheWritePrice: 3.75, - } - - sonnet4PremiumPricing = ModelPricing{ - InputPrice: 6.0, - OutputPrice: 22.5, - CacheReadPrice: 0.6, - CacheWritePrice: 7.5, - } - - haiku4Pricing = ModelPricing{ - InputPrice: 1.0, - OutputPrice: 5.0, - CacheReadPrice: 0.1, - CacheWritePrice: 1.25, - } - - haiku35Pricing = ModelPricing{ - InputPrice: 0.8, - OutputPrice: 4.0, - CacheReadPrice: 0.08, - CacheWritePrice: 1.0, - } - - sonnet35Pricing = ModelPricing{ - InputPrice: 3.0, - OutputPrice: 15.0, - CacheReadPrice: 0.3, - CacheWritePrice: 3.75, + opus46PremiumPricing = ModelPricing{ + InputPrice: 10.0, + OutputPrice: 37.5, + CacheReadPrice: 1.0, + CacheWritePrice5Minute: 12.5, + CacheWritePrice1Hour: 20.0, } opus45Pricing = ModelPricing{ - InputPrice: 5.0, - OutputPrice: 25.0, - CacheReadPrice: 0.5, - CacheWritePrice: 6.25, + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice5Minute: 6.25, + CacheWritePrice1Hour: 10.0, + } + + opus4Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice5Minute: 18.75, + CacheWritePrice1Hour: 30.0, + } + + sonnet46StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet46PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, } sonnet45StandardPricing = ModelPricing{ - InputPrice: 3.0, - OutputPrice: 15.0, - CacheReadPrice: 0.3, - CacheWritePrice: 3.75, + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, } sonnet45PremiumPricing = ModelPricing{ - InputPrice: 6.0, - OutputPrice: 22.5, - CacheReadPrice: 0.6, - CacheWritePrice: 7.5, + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, + } + + sonnet4StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet4PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice5Minute: 7.5, + CacheWritePrice1Hour: 12.0, + } + + sonnet37Pricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, + } + + sonnet35Pricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice5Minute: 3.75, + CacheWritePrice1Hour: 6.0, } haiku45Pricing = ModelPricing{ - InputPrice: 1.0, - OutputPrice: 5.0, - CacheReadPrice: 0.1, - CacheWritePrice: 1.25, + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice5Minute: 1.25, + CacheWritePrice1Hour: 2.0, + } + + haiku4Pricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice5Minute: 1.25, + CacheWritePrice1Hour: 2.0, + } + + haiku35Pricing = ModelPricing{ + InputPrice: 0.8, + OutputPrice: 4.0, + CacheReadPrice: 0.08, + CacheWritePrice5Minute: 1.0, + CacheWritePrice1Hour: 1.6, } haiku3Pricing = ModelPricing{ - InputPrice: 0.25, - OutputPrice: 1.25, - CacheReadPrice: 0.03, - CacheWritePrice: 0.3, + InputPrice: 0.25, + OutputPrice: 1.25, + CacheReadPrice: 0.03, + CacheWritePrice5Minute: 0.3, + CacheWritePrice1Hour: 0.5, } opus3Pricing = ModelPricing{ - InputPrice: 15.0, - OutputPrice: 75.0, - CacheReadPrice: 1.5, - CacheWritePrice: 18.75, + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice5Minute: 18.75, + CacheWritePrice1Hour: 30.0, } modelFamilies = []modelFamily{ { - pattern: regexp.MustCompile(`^claude-opus-4-5-`), + pattern: regexp.MustCompile(`^claude-opus-4-6(?:-|$)`), + standardPricing: opus46StandardPricing, + premiumPricing: &opus46PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-opus-4-5(?:-|$)`), standardPricing: opus45Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:opus-4-|4-opus-|opus-4-1-)`), + pattern: regexp.MustCompile(`^claude-(?:opus-4(?:-|$)|4-opus-)`), standardPricing: opus4Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:opus-3-|3-opus-)`), + pattern: regexp.MustCompile(`^claude-(?:opus-3(?:-|$)|3-opus-)`), standardPricing: opus3Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5-|4-5-sonnet-)`), + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-6(?:-|$)|4-6-sonnet-)`), + standardPricing: sonnet46StandardPricing, + premiumPricing: &sonnet46PremiumPricing, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5(?:-|$)|4-5-sonnet-)`), standardPricing: sonnet45StandardPricing, premiumPricing: &sonnet45PremiumPricing, }, { - pattern: regexp.MustCompile(`^claude-3-7-sonnet-`), + pattern: regexp.MustCompile(`^claude-(?:sonnet-4(?:-|$)|4-sonnet-)`), standardPricing: sonnet4StandardPricing, premiumPricing: &sonnet4PremiumPricing, }, { - pattern: regexp.MustCompile(`^claude-(?:sonnet-4-|4-sonnet-)`), - standardPricing: sonnet4StandardPricing, - premiumPricing: &sonnet4PremiumPricing, + pattern: regexp.MustCompile(`^claude-3-7-sonnet(?:-|$)`), + standardPricing: sonnet37Pricing, + premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-5-sonnet-`), + pattern: regexp.MustCompile(`^claude-3-5-sonnet(?:-|$)`), standardPricing: sonnet35Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-(?:haiku-4-5-|4-5-haiku-)`), + pattern: regexp.MustCompile(`^claude-(?:haiku-4-5(?:-|$)|4-5-haiku-)`), standardPricing: haiku45Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-haiku-4-`), + pattern: regexp.MustCompile(`^claude-haiku-4(?:-|$)`), standardPricing: haiku4Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-5-haiku-`), + pattern: regexp.MustCompile(`^claude-3-5-haiku(?:-|$)`), standardPricing: haiku35Pricing, premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-haiku-`), + pattern: regexp.MustCompile(`^claude-3-haiku(?:-|$)`), standardPricing: haiku3Pricing, premiumPricing: nil, }, @@ -243,68 +319,211 @@ func getPricing(model string, contextWindow int) ModelPricing { func calculateCost(stats UsageStats, model string, contextWindow int) float64 { pricing := getPricing(model, contextWindow) + cacheCreationCost := 0.0 + if stats.CacheCreation5MinuteInputTokens > 0 || stats.CacheCreation1HourInputTokens > 0 { + cacheCreationCost = float64(stats.CacheCreation5MinuteInputTokens)*pricing.CacheWritePrice5Minute + + float64(stats.CacheCreation1HourInputTokens)*pricing.CacheWritePrice1Hour + } else { + // Backward compatibility for usage files generated before TTL split tracking. + cacheCreationCost = float64(stats.CacheCreationInputTokens) * pricing.CacheWritePrice5Minute + } + cost := (float64(stats.InputTokens)*pricing.InputPrice + float64(stats.OutputTokens)*pricing.OutputPrice + float64(stats.CacheReadInputTokens)*pricing.CacheReadPrice + - float64(stats.CacheCreationInputTokens)*pricing.CacheWritePrice) / 1_000_000 + cacheCreationCost) / 1_000_000 return math.Round(cost*100) / 100 } +func roundCost(cost float64) float64 { + return math.Round(cost*100) / 100 +} + +func normalizeCombinations(combinations []CostCombination) { + for index := range combinations { + if combinations[index].ByUser == nil { + combinations[index].ByUser = make(map[string]UsageStats) + } + } +} + +func addUsageToCombinations( + combinations *[]CostCombination, + model string, + contextWindow int, + weekStartUnix int64, + messagesCount int, + inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64, + user string, +) { + var matchedCombination *CostCombination + for index := range *combinations { + combination := &(*combinations)[index] + if combination.Model == model && combination.ContextWindow == contextWindow && combination.WeekStartUnix == weekStartUnix { + matchedCombination = combination + break + } + } + + if matchedCombination == nil { + newCombination := CostCombination{ + Model: model, + ContextWindow: contextWindow, + WeekStartUnix: weekStartUnix, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), + } + *combinations = append(*combinations, newCombination) + matchedCombination = &(*combinations)[len(*combinations)-1] + } + + if cacheCreationTokens == 0 { + cacheCreationTokens = cacheCreation5MinuteTokens + cacheCreation1HourTokens + } + + matchedCombination.Total.RequestCount++ + matchedCombination.Total.MessagesCount += messagesCount + matchedCombination.Total.InputTokens += inputTokens + matchedCombination.Total.OutputTokens += outputTokens + matchedCombination.Total.CacheReadInputTokens += cacheReadTokens + matchedCombination.Total.CacheCreationInputTokens += cacheCreationTokens + matchedCombination.Total.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens + matchedCombination.Total.CacheCreation1HourInputTokens += cacheCreation1HourTokens + + if user != "" { + userStats := matchedCombination.ByUser[user] + userStats.RequestCount++ + userStats.MessagesCount += messagesCount + userStats.InputTokens += inputTokens + userStats.OutputTokens += outputTokens + userStats.CacheReadInputTokens += cacheReadTokens + userStats.CacheCreationInputTokens += cacheCreationTokens + userStats.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens + userStats.CacheCreation1HourInputTokens += cacheCreation1HourTokens + matchedCombination.ByUser[user] = userStats + } +} + +func buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) { + result := make([]CostCombinationJSON, len(combinations)) + var totalCost float64 + + for index, combination := range combinations { + combinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ContextWindow) + totalCost += combinationTotalCost + + combinationJSON := CostCombinationJSON{ + Model: combination.Model, + ContextWindow: combination.ContextWindow, + WeekStartUnix: combination.WeekStartUnix, + Total: UsageStatsJSON{ + RequestCount: combination.Total.RequestCount, + MessagesCount: combination.Total.MessagesCount, + InputTokens: combination.Total.InputTokens, + OutputTokens: combination.Total.OutputTokens, + CacheReadInputTokens: combination.Total.CacheReadInputTokens, + CacheCreationInputTokens: combination.Total.CacheCreationInputTokens, + CacheCreation5MinuteInputTokens: combination.Total.CacheCreation5MinuteInputTokens, + CacheCreation1HourInputTokens: combination.Total.CacheCreation1HourInputTokens, + CostUSD: combinationTotalCost, + }, + ByUser: make(map[string]UsageStatsJSON), + } + + for user, userStats := range combination.ByUser { + userCost := calculateCost(userStats, combination.Model, combination.ContextWindow) + if aggregateUserCosts != nil { + aggregateUserCosts[user] += userCost + } + + combinationJSON.ByUser[user] = UsageStatsJSON{ + RequestCount: userStats.RequestCount, + MessagesCount: userStats.MessagesCount, + InputTokens: userStats.InputTokens, + OutputTokens: userStats.OutputTokens, + CacheReadInputTokens: userStats.CacheReadInputTokens, + CacheCreationInputTokens: userStats.CacheCreationInputTokens, + CacheCreation5MinuteInputTokens: userStats.CacheCreation5MinuteInputTokens, + CacheCreation1HourInputTokens: userStats.CacheCreation1HourInputTokens, + CostUSD: userCost, + } + } + + result[index] = combinationJSON + } + + return result, roundCost(totalCost) +} + +func formatUTCOffsetLabel(timestamp time.Time) string { + _, offsetSeconds := timestamp.Zone() + sign := "+" + if offsetSeconds < 0 { + sign = "-" + offsetSeconds = -offsetSeconds + } + offsetHours := offsetSeconds / 3600 + offsetMinutes := (offsetSeconds % 3600) / 60 + if offsetMinutes == 0 { + return fmt.Sprintf("UTC%s%d", sign, offsetHours) + } + return fmt.Sprintf("UTC%s%d:%02d", sign, offsetHours, offsetMinutes) +} + +func formatWeekStartKey(cycleStartAt time.Time) string { + localCycleStart := cycleStartAt.In(time.Local) + return fmt.Sprintf("%s %s", localCycleStart.Format("2006-01-02 15:04:05"), formatUTCOffsetLabel(localCycleStart)) +} + +func buildByWeekCost(combinations []CostCombination) map[string]float64 { + byWeek := make(map[string]float64) + for _, combination := range combinations { + if combination.WeekStartUnix <= 0 { + continue + } + weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC() + weekKey := formatWeekStartKey(weekStartAt) + byWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ContextWindow) + } + for weekKey, weekCost := range byWeek { + byWeek[weekKey] = roundCost(weekCost) + } + return byWeek +} + +func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 { + if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() { + return 0 + } + windowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute + return cycleHint.ResetAt.UTC().Add(-windowDuration).Unix() +} + func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { u.mutex.Lock() defer u.mutex.Unlock() result := &AggregatedUsageJSON{ - LastUpdated: u.LastUpdated, - Combinations: make([]CostCombinationJSON, len(u.Combinations)), + LastUpdated: u.LastUpdated, Costs: CostsSummaryJSON{ TotalUSD: 0, ByUser: make(map[string]float64), + ByWeek: make(map[string]float64), }, } - for i, combo := range u.Combinations { - totalCost := calculateCost(combo.Total, combo.Model, combo.ContextWindow) + globalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser) + result.Combinations = globalCombinationsJSON + result.Costs.TotalUSD = totalCost + result.Costs.ByWeek = buildByWeekCost(u.Combinations) - result.Costs.TotalUSD += totalCost - - comboJSON := CostCombinationJSON{ - Model: combo.Model, - ContextWindow: combo.ContextWindow, - Total: UsageStatsJSON{ - RequestCount: combo.Total.RequestCount, - MessagesCount: combo.Total.MessagesCount, - InputTokens: combo.Total.InputTokens, - OutputTokens: combo.Total.OutputTokens, - CacheReadInputTokens: combo.Total.CacheReadInputTokens, - CacheCreationInputTokens: combo.Total.CacheCreationInputTokens, - CostUSD: totalCost, - }, - ByUser: make(map[string]UsageStatsJSON), - } - - for user, userStats := range combo.ByUser { - userCost := calculateCost(userStats, combo.Model, combo.ContextWindow) - result.Costs.ByUser[user] += userCost - - comboJSON.ByUser[user] = UsageStatsJSON{ - RequestCount: userStats.RequestCount, - MessagesCount: userStats.MessagesCount, - InputTokens: userStats.InputTokens, - OutputTokens: userStats.OutputTokens, - CacheReadInputTokens: userStats.CacheReadInputTokens, - CacheCreationInputTokens: userStats.CacheCreationInputTokens, - CostUSD: userCost, - } - } - - result.Combinations[i] = comboJSON + if len(result.Costs.ByWeek) == 0 { + result.Costs.ByWeek = nil } - result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100 for user, cost := range result.Costs.ByUser { - result.Costs.ByUser[user] = math.Round(cost*100) / 100 + result.Costs.ByUser[user] = roundCost(cost) } return result @@ -314,6 +533,9 @@ func (u *AggregatedUsage) Load() error { u.mutex.Lock() defer u.mutex.Unlock() + u.LastUpdated = time.Time{} + u.Combinations = nil + data, err := os.ReadFile(u.filePath) if err != nil { if os.IsNotExist(err) { @@ -334,12 +556,7 @@ func (u *AggregatedUsage) Load() error { u.LastUpdated = temp.LastUpdated u.Combinations = temp.Combinations - - for i := range u.Combinations { - if u.Combinations[i].ByUser == nil { - u.Combinations[i].ByUser = make(map[string]UsageStats) - } - } + normalizeCombinations(u.Combinations) return nil } @@ -367,58 +584,42 @@ func (u *AggregatedUsage) Save() error { return err } -func (u *AggregatedUsage) AddUsage(model string, contextWindow int, messagesCount int, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens int64, user string) error { +func (u *AggregatedUsage) AddUsage( + model string, + contextWindow int, + messagesCount int, + inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64, + user string, +) error { + return u.AddUsageWithCycleHint(model, contextWindow, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user, time.Now(), nil) +} + +func (u *AggregatedUsage) AddUsageWithCycleHint( + model string, + contextWindow int, + messagesCount int, + inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64, + user string, + observedAt time.Time, + cycleHint *WeeklyCycleHint, +) error { if model == "" { return E.New("model cannot be empty") } if contextWindow <= 0 { return E.New("contextWindow must be positive") } + if observedAt.IsZero() { + observedAt = time.Now() + } u.mutex.Lock() defer u.mutex.Unlock() - u.LastUpdated = time.Now() + u.LastUpdated = observedAt + weekStartUnix := deriveWeekStartUnix(cycleHint) - // Find or create combination - var combo *CostCombination - for i := range u.Combinations { - if u.Combinations[i].Model == model && u.Combinations[i].ContextWindow == contextWindow { - combo = &u.Combinations[i] - break - } - } - - if combo == nil { - newCombo := CostCombination{ - Model: model, - ContextWindow: contextWindow, - Total: UsageStats{}, - ByUser: make(map[string]UsageStats), - } - u.Combinations = append(u.Combinations, newCombo) - combo = &u.Combinations[len(u.Combinations)-1] - } - - // Update total stats - combo.Total.RequestCount++ - combo.Total.MessagesCount += messagesCount - combo.Total.InputTokens += inputTokens - combo.Total.OutputTokens += outputTokens - combo.Total.CacheReadInputTokens += cacheReadTokens - combo.Total.CacheCreationInputTokens += cacheCreationTokens - - // Update per-user stats if user is specified - if user != "" { - userStats := combo.ByUser[user] - userStats.RequestCount++ - userStats.MessagesCount += messagesCount - userStats.InputTokens += inputTokens - userStats.OutputTokens += outputTokens - userStats.CacheReadInputTokens += cacheReadTokens - userStats.CacheCreationInputTokens += cacheCreationTokens - combo.ByUser[user] = userStats - } + addUsageToCombinations(&u.Combinations, model, contextWindow, weekStartUnix, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user) go u.scheduleSave() diff --git a/service/ocm/service.go b/service/ocm/service.go index fc655f67..2354d159 100644 --- a/service/ocm/service.go +++ b/service/ocm/service.go @@ -10,6 +10,7 @@ import ( "mime" "net" "net/http" + "strconv" "strings" "sync" "time" @@ -71,6 +72,57 @@ func isHopByHopHeader(header string) bool { } } +func normalizeRateLimitIdentifier(limitIdentifier string) string { + trimmedIdentifier := strings.TrimSpace(strings.ToLower(limitIdentifier)) + if trimmedIdentifier == "" { + return "" + } + return strings.ReplaceAll(trimmedIdentifier, "_", "-") +} + +func parseInt64Header(headers http.Header, headerName string) (int64, bool) { + headerValue := strings.TrimSpace(headers.Get(headerName)) + if headerValue == "" { + return 0, false + } + parsedValue, parseError := strconv.ParseInt(headerValue, 10, 64) + if parseError != nil { + return 0, false + } + return parsedValue, true +} + +func weeklyCycleHintForLimit(headers http.Header, limitIdentifier string) *WeeklyCycleHint { + normalizedLimitIdentifier := normalizeRateLimitIdentifier(limitIdentifier) + if normalizedLimitIdentifier == "" { + return nil + } + + windowHeader := "x-" + normalizedLimitIdentifier + "-secondary-window-minutes" + resetHeader := "x-" + normalizedLimitIdentifier + "-secondary-reset-at" + + windowMinutes, hasWindowMinutes := parseInt64Header(headers, windowHeader) + resetAtUnix, hasResetAt := parseInt64Header(headers, resetHeader) + if !hasWindowMinutes || !hasResetAt || windowMinutes <= 0 || resetAtUnix <= 0 { + return nil + } + + return &WeeklyCycleHint{ + WindowMinutes: windowMinutes, + ResetAt: time.Unix(resetAtUnix, 0).UTC(), + } +} + +func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint { + activeLimitIdentifier := normalizeRateLimitIdentifier(headers.Get("x-codex-active-limit")) + if activeLimitIdentifier != "" { + if activeHint := weeklyCycleHintForLimit(headers, activeLimitIdentifier); activeHint != nil { + return activeHint + } + } + return weeklyCycleHintForLimit(headers, "codex") +} + type Service struct { boxService.Adapter ctx context.Context @@ -404,9 +456,12 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) { isChatCompletions := path == "/v1/chat/completions" + weeklyCycleHint := extractWeeklyCycleHint(response.Header) mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) isStreaming := err == nil && mediaType == "text/event-stream" - + if !isStreaming && !isChatCompletions && response.Header.Get("Content-Type") == "" { + isStreaming = true + } if !isStreaming { bodyBytes, err := io.ReadAll(response.Body) if err != nil { @@ -414,13 +469,14 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons return } - var responseModel string + var responseModel, serviceTier string var inputTokens, outputTokens, cachedTokens int64 if isChatCompletions { var chatCompletion openai.ChatCompletion if json.Unmarshal(bodyBytes, &chatCompletion) == nil { responseModel = chatCompletion.Model + serviceTier = string(chatCompletion.ServiceTier) inputTokens = chatCompletion.Usage.PromptTokens outputTokens = chatCompletion.Usage.CompletionTokens cachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens @@ -429,6 +485,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons var responsesResponse responses.Response if json.Unmarshal(bodyBytes, &responsesResponse) == nil { responseModel = string(responsesResponse.Model) + serviceTier = string(responsesResponse.ServiceTier) inputTokens = responsesResponse.Usage.InputTokens outputTokens = responsesResponse.Usage.OutputTokens cachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens @@ -440,7 +497,16 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons responseModel = requestModel } if responseModel != "" { - s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + s.usageTracker.AddUsageWithCycleHint( + responseModel, + inputTokens, + outputTokens, + cachedTokens, + serviceTier, + username, + time.Now(), + weeklyCycleHint, + ) } } @@ -455,7 +521,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons } var inputTokens, outputTokens, cachedTokens int64 - var responseModel string + var responseModel, serviceTier string buffer := make([]byte, buf.BufferSize) var leftover []byte @@ -490,6 +556,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if chatChunk.Model != "" { responseModel = chatChunk.Model } + if chatChunk.ServiceTier != "" { + serviceTier = string(chatChunk.ServiceTier) + } if chatChunk.Usage.PromptTokens > 0 { inputTokens = chatChunk.Usage.PromptTokens cachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens @@ -506,6 +575,9 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if string(completedEvent.Response.Model) != "" { responseModel = string(completedEvent.Response.Model) } + if completedEvent.Response.ServiceTier != "" { + serviceTier = string(completedEvent.Response.ServiceTier) + } if completedEvent.Response.Usage.InputTokens > 0 { inputTokens = completedEvent.Response.Usage.InputTokens cachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens @@ -534,7 +606,16 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons if inputTokens > 0 || outputTokens > 0 { if responseModel != "" { - s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + s.usageTracker.AddUsageWithCycleHint( + responseModel, + inputTokens, + outputTokens, + cachedTokens, + serviceTier, + username, + time.Now(), + weeklyCycleHint, + ) } } return diff --git a/service/ocm/service_usage.go b/service/ocm/service_usage.go index 7089f4d3..a4c1d1c8 100644 --- a/service/ocm/service_usage.go +++ b/service/ocm/service_usage.go @@ -2,9 +2,11 @@ package ocm import ( "encoding/json" + "fmt" "math" "os" "regexp" + "strings" "sync" "time" @@ -42,9 +44,11 @@ func (u *UsageStats) UnmarshalJSON(data []byte) error { } type CostCombination struct { - Model string `json:"model"` - Total UsageStats `json:"total"` - ByUser map[string]UsageStats `json:"by_user"` + Model string `json:"model"` + ServiceTier string `json:"service_tier,omitempty"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` + Total UsageStats `json:"total"` + ByUser map[string]UsageStats `json:"by_user"` } type AggregatedUsage struct { @@ -68,14 +72,17 @@ type UsageStatsJSON struct { } type CostCombinationJSON struct { - Model string `json:"model"` - Total UsageStatsJSON `json:"total"` - ByUser map[string]UsageStatsJSON `json:"by_user"` + Model string `json:"model"` + ServiceTier string `json:"service_tier,omitempty"` + WeekStartUnix int64 `json:"week_start_unix,omitempty"` + Total UsageStatsJSON `json:"total"` + ByUser map[string]UsageStatsJSON `json:"by_user"` } type CostsSummaryJSON struct { TotalUSD float64 `json:"total_usd"` ByUser map[string]float64 `json:"by_user"` + ByWeek map[string]float64 `json:"by_week,omitempty"` } type AggregatedUsageJSON struct { @@ -84,6 +91,11 @@ type AggregatedUsageJSON struct { Combinations []CostCombinationJSON `json:"combinations"` } +type WeeklyCycleHint struct { + WindowMinutes int64 + ResetAt time.Time +} + type ModelPricing struct { InputPrice float64 OutputPrice float64 @@ -95,7 +107,123 @@ type modelFamily struct { pricing ModelPricing } +const ( + serviceTierAuto = "auto" + serviceTierDefault = "default" + serviceTierFlex = "flex" + serviceTierPriority = "priority" + serviceTierScale = "scale" +) + var ( + gpt52Pricing = ModelPricing{ + InputPrice: 1.75, + OutputPrice: 14.0, + CachedInputPrice: 0.175, + } + + gpt5Pricing = ModelPricing{ + InputPrice: 1.25, + OutputPrice: 10.0, + CachedInputPrice: 0.125, + } + + gpt5MiniPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 2.0, + CachedInputPrice: 0.025, + } + + gpt5NanoPricing = ModelPricing{ + InputPrice: 0.05, + OutputPrice: 0.4, + CachedInputPrice: 0.005, + } + + gpt52CodexPricing = ModelPricing{ + InputPrice: 1.75, + OutputPrice: 14.0, + CachedInputPrice: 0.175, + } + + gpt51CodexPricing = ModelPricing{ + InputPrice: 1.25, + OutputPrice: 10.0, + CachedInputPrice: 0.125, + } + + gpt51CodexMiniPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 2.0, + CachedInputPrice: 0.025, + } + + gpt52ProPricing = ModelPricing{ + InputPrice: 21.0, + OutputPrice: 168.0, + CachedInputPrice: 21.0, + } + + gpt5ProPricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 120.0, + CachedInputPrice: 15.0, + } + + gpt52FlexPricing = ModelPricing{ + InputPrice: 0.875, + OutputPrice: 7.0, + CachedInputPrice: 0.0875, + } + + gpt5FlexPricing = ModelPricing{ + InputPrice: 0.625, + OutputPrice: 5.0, + CachedInputPrice: 0.0625, + } + + gpt5MiniFlexPricing = ModelPricing{ + InputPrice: 0.125, + OutputPrice: 1.0, + CachedInputPrice: 0.0125, + } + + gpt5NanoFlexPricing = ModelPricing{ + InputPrice: 0.025, + OutputPrice: 0.2, + CachedInputPrice: 0.0025, + } + + gpt52PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 28.0, + CachedInputPrice: 0.35, + } + + gpt5PriorityPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 20.0, + CachedInputPrice: 0.25, + } + + gpt5MiniPriorityPricing = ModelPricing{ + InputPrice: 0.45, + OutputPrice: 3.6, + CachedInputPrice: 0.045, + } + + gpt52CodexPriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 28.0, + CachedInputPrice: 0.35, + } + + gpt51CodexPriorityPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 20.0, + CachedInputPrice: 0.25, + } + gpt4oPricing = ModelPricing{ InputPrice: 2.5, OutputPrice: 10.0, @@ -111,7 +239,19 @@ var ( gpt4oAudioPricing = ModelPricing{ InputPrice: 2.5, OutputPrice: 10.0, - CachedInputPrice: 1.25, + CachedInputPrice: 2.5, + } + + gpt4oMiniAudioPricing = ModelPricing{ + InputPrice: 0.15, + OutputPrice: 0.6, + CachedInputPrice: 0.15, + } + + gptAudioMiniPricing = ModelPricing{ + InputPrice: 0.6, + OutputPrice: 2.4, + CachedInputPrice: 0.6, } o1Pricing = ModelPricing{ @@ -120,6 +260,12 @@ var ( CachedInputPrice: 7.5, } + o1ProPricing = ModelPricing{ + InputPrice: 150.0, + OutputPrice: 600.0, + CachedInputPrice: 150.0, + } + o1MiniPricing = ModelPricing{ InputPrice: 1.1, OutputPrice: 4.4, @@ -135,13 +281,55 @@ var ( o3Pricing = ModelPricing{ InputPrice: 2.0, OutputPrice: 8.0, - CachedInputPrice: 1.0, + CachedInputPrice: 0.5, + } + + o3ProPricing = ModelPricing{ + InputPrice: 20.0, + OutputPrice: 80.0, + CachedInputPrice: 20.0, + } + + o3DeepResearchPricing = ModelPricing{ + InputPrice: 10.0, + OutputPrice: 40.0, + CachedInputPrice: 2.5, } o4MiniPricing = ModelPricing{ InputPrice: 1.1, OutputPrice: 4.4, - CachedInputPrice: 0.55, + CachedInputPrice: 0.275, + } + + o4MiniDeepResearchPricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + o3FlexPricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 4.0, + CachedInputPrice: 0.25, + } + + o4MiniFlexPricing = ModelPricing{ + InputPrice: 0.55, + OutputPrice: 2.2, + CachedInputPrice: 0.138, + } + + o3PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 14.0, + CachedInputPrice: 0.875, + } + + o4MiniPriorityPricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, } gpt41Pricing = ModelPricing{ @@ -162,69 +350,374 @@ var ( CachedInputPrice: 0.025, } - modelFamilies = []modelFamily{ + gpt41PriorityPricing = ModelPricing{ + InputPrice: 3.5, + OutputPrice: 14.0, + CachedInputPrice: 0.875, + } + + gpt41MiniPriorityPricing = ModelPricing{ + InputPrice: 0.7, + OutputPrice: 2.8, + CachedInputPrice: 0.175, + } + + gpt41NanoPriorityPricing = ModelPricing{ + InputPrice: 0.2, + OutputPrice: 0.8, + CachedInputPrice: 0.05, + } + + gpt4oPriorityPricing = ModelPricing{ + InputPrice: 4.25, + OutputPrice: 17.0, + CachedInputPrice: 2.125, + } + + gpt4oMiniPriorityPricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 1.0, + CachedInputPrice: 0.125, + } + + standardModelFamilies = []modelFamily{ { - pattern: regexp.MustCompile(`^gpt-4\.1-nano`), - pricing: gpt41NanoPricing, + pattern: regexp.MustCompile(`^gpt-5\.3-codex(?:$|-)`), + pricing: gpt52CodexPricing, }, { - pattern: regexp.MustCompile(`^gpt-4\.1-mini`), - pricing: gpt41MiniPricing, + pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`), + pricing: gpt52CodexPricing, }, { - pattern: regexp.MustCompile(`^gpt-4\.1`), - pricing: gpt41Pricing, + pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`), + pricing: gpt51CodexPricing, }, { - pattern: regexp.MustCompile(`^o4-mini`), + pattern: regexp.MustCompile(`^gpt-5\.1-codex-mini(?:$|-)`), + pricing: gpt51CodexMiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`), + pricing: gpt51CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`), + pricing: gpt51CodexMiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`), + pricing: gpt51CodexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-chat-latest$`), + pricing: gpt52Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-chat-latest$`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-chat-latest$`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-pro(?:$|-)`), + pricing: gpt52ProPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-pro(?:$|-)`), + pricing: gpt5ProPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`), + pricing: gpt5NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5Pricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini-deep-research(?:$|-)`), + pricing: o4MiniDeepResearchPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), pricing: o4MiniPricing, }, { - pattern: regexp.MustCompile(`^o3-mini`), + pattern: regexp.MustCompile(`^o3-pro(?:$|-)`), + pricing: o3ProPricing, + }, + { + pattern: regexp.MustCompile(`^o3-deep-research(?:$|-)`), + pricing: o3DeepResearchPricing, + }, + { + pattern: regexp.MustCompile(`^o3-mini(?:$|-)`), pricing: o3MiniPricing, }, { - pattern: regexp.MustCompile(`^o3`), + pattern: regexp.MustCompile(`^o3(?:$|-)`), pricing: o3Pricing, }, { - pattern: regexp.MustCompile(`^o1-mini`), + pattern: regexp.MustCompile(`^o1-pro(?:$|-)`), + pricing: o1ProPricing, + }, + { + pattern: regexp.MustCompile(`^o1-mini(?:$|-)`), pricing: o1MiniPricing, }, { - pattern: regexp.MustCompile(`^o1`), + pattern: regexp.MustCompile(`^o1(?:$|-)`), pricing: o1Pricing, }, { - pattern: regexp.MustCompile(`^gpt-4o-audio`), + pattern: regexp.MustCompile(`^gpt-4o-mini-audio(?:$|-)`), + pricing: gpt4oMiniAudioPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-audio-mini(?:$|-)`), + pricing: gptAudioMiniPricing, + }, + { + pattern: regexp.MustCompile(`^(?:gpt-4o-audio|gpt-audio)(?:$|-)`), pricing: gpt4oAudioPricing, }, { - pattern: regexp.MustCompile(`^gpt-4o-mini`), + pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`), + pricing: gpt41NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`), + pricing: gpt41MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`), + pricing: gpt41Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`), pricing: gpt4oMiniPricing, }, { - pattern: regexp.MustCompile(`^gpt-4o`), + pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`), pricing: gpt4oPricing, }, { - pattern: regexp.MustCompile(`^chatgpt-4o`), + pattern: regexp.MustCompile(`^chatgpt-4o(?:$|-)`), pricing: gpt4oPricing, }, } + + flexModelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-nano(?:$|-)`), + pricing: gpt5NanoFlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52FlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5FlexPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5FlexPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), + pricing: o4MiniFlexPricing, + }, + { + pattern: regexp.MustCompile(`^o3(?:$|-)`), + pricing: o3FlexPricing, + }, + } + + priorityModelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-5\.3-codex(?:$|-)`), + pricing: gpt52CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`), + pricing: gpt52CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex-max(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`), + pricing: gpt5MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`), + pricing: gpt51CodexPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5-mini(?:$|-)`), + pricing: gpt5MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.2(?:$|-)`), + pricing: gpt52PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5\.1(?:$|-)`), + pricing: gpt5PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-5(?:$|-)`), + pricing: gpt5PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini(?:$|-)`), + pricing: o4MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^o3(?:$|-)`), + pricing: o3PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-nano(?:$|-)`), + pricing: gpt41NanoPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini(?:$|-)`), + pricing: gpt41MiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1(?:$|-)`), + pricing: gpt41PriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini(?:$|-)`), + pricing: gpt4oMiniPriorityPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o(?:$|-)`), + pricing: gpt4oPriorityPricing, + }, + } ) -func getPricing(model string) ModelPricing { +func modelFamiliesForTier(serviceTier string) []modelFamily { + switch serviceTier { + case serviceTierFlex: + return flexModelFamilies + case serviceTierPriority: + return priorityModelFamilies + default: + return standardModelFamilies + } +} + +func findPricingInFamilies(model string, modelFamilies []modelFamily) (ModelPricing, bool) { for _, family := range modelFamilies { if family.pattern.MatchString(model) { - return family.pricing + return family.pricing, true } } + return ModelPricing{}, false +} + +func normalizeServiceTier(serviceTier string) string { + switch strings.ToLower(strings.TrimSpace(serviceTier)) { + case "", serviceTierAuto, serviceTierDefault: + return serviceTierDefault + case serviceTierFlex: + return serviceTierFlex + case serviceTierPriority: + return serviceTierPriority + case serviceTierScale: + // Scale-tier requests are prepaid differently and not listed in this usage file. + return serviceTierDefault + default: + return serviceTierDefault + } +} + +func getPricing(model string, serviceTier string) ModelPricing { + normalizedServiceTier := normalizeServiceTier(serviceTier) + modelFamilies := modelFamiliesForTier(normalizedServiceTier) + + if pricing, found := findPricingInFamilies(model, modelFamilies); found { + return pricing + } + + normalizedModel := normalizeGPT5Model(model) + if normalizedModel != model { + if pricing, found := findPricingInFamilies(normalizedModel, modelFamilies); found { + return pricing + } + } + + if normalizedServiceTier != serviceTierDefault { + if pricing, found := findPricingInFamilies(model, standardModelFamilies); found { + return pricing + } + if normalizedModel != model { + if pricing, found := findPricingInFamilies(normalizedModel, standardModelFamilies); found { + return pricing + } + } + } + return gpt4oPricing } -func calculateCost(stats UsageStats, model string) float64 { - pricing := getPricing(model) +func normalizeGPT5Model(model string) string { + if !strings.HasPrefix(model, "gpt-5.") { + return model + } + + switch { + case strings.Contains(model, "-codex-mini"): + return "gpt-5.1-codex-mini" + case strings.Contains(model, "-codex-max"): + return "gpt-5.1-codex-max" + case strings.Contains(model, "-codex"): + return "gpt-5.3-codex" + case strings.Contains(model, "-chat-latest"): + return "gpt-5.2-chat-latest" + case strings.Contains(model, "-pro"): + return "gpt-5.2-pro" + case strings.Contains(model, "-mini"): + return "gpt-5-mini" + case strings.Contains(model, "-nano"): + return "gpt-5-nano" + default: + return "gpt-5.2" + } +} + +func calculateCost(stats UsageStats, model string, serviceTier string) float64 { + pricing := getPricing(model, serviceTier) regularInputTokens := stats.InputTokens - stats.CachedTokens if regularInputTokens < 0 { @@ -238,41 +731,89 @@ func calculateCost(stats UsageStats, model string) float64 { return math.Round(cost*100) / 100 } -func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { - u.mutex.Lock() - defer u.mutex.Unlock() +func roundCost(cost float64) float64 { + return math.Round(cost*100) / 100 +} - result := &AggregatedUsageJSON{ - LastUpdated: u.LastUpdated, - Combinations: make([]CostCombinationJSON, len(u.Combinations)), - Costs: CostsSummaryJSON{ - TotalUSD: 0, - ByUser: make(map[string]float64), - }, +func normalizeCombinations(combinations []CostCombination) { + for index := range combinations { + combinations[index].ServiceTier = normalizeServiceTier(combinations[index].ServiceTier) + if combinations[index].ByUser == nil { + combinations[index].ByUser = make(map[string]UsageStats) + } + } +} + +func addUsageToCombinations(combinations *[]CostCombination, model string, serviceTier string, weekStartUnix int64, user string, inputTokens, outputTokens, cachedTokens int64) { + var matchedCombination *CostCombination + for index := range *combinations { + combination := &(*combinations)[index] + combinationServiceTier := normalizeServiceTier(combination.ServiceTier) + if combination.ServiceTier != combinationServiceTier { + combination.ServiceTier = combinationServiceTier + } + if combination.Model == model && combinationServiceTier == serviceTier && combination.WeekStartUnix == weekStartUnix { + matchedCombination = combination + break + } } - for i, combo := range u.Combinations { - totalCost := calculateCost(combo.Total, combo.Model) + if matchedCombination == nil { + newCombination := CostCombination{ + Model: model, + ServiceTier: serviceTier, + WeekStartUnix: weekStartUnix, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), + } + *combinations = append(*combinations, newCombination) + matchedCombination = &(*combinations)[len(*combinations)-1] + } - result.Costs.TotalUSD += totalCost + matchedCombination.Total.RequestCount++ + matchedCombination.Total.InputTokens += inputTokens + matchedCombination.Total.OutputTokens += outputTokens + matchedCombination.Total.CachedTokens += cachedTokens - comboJSON := CostCombinationJSON{ - Model: combo.Model, + if user != "" { + userStats := matchedCombination.ByUser[user] + userStats.RequestCount++ + userStats.InputTokens += inputTokens + userStats.OutputTokens += outputTokens + userStats.CachedTokens += cachedTokens + matchedCombination.ByUser[user] = userStats + } +} + +func buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) { + result := make([]CostCombinationJSON, len(combinations)) + var totalCost float64 + + for index, combination := range combinations { + combinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ServiceTier) + totalCost += combinationTotalCost + + combinationJSON := CostCombinationJSON{ + Model: combination.Model, + ServiceTier: combination.ServiceTier, + WeekStartUnix: combination.WeekStartUnix, Total: UsageStatsJSON{ - RequestCount: combo.Total.RequestCount, - InputTokens: combo.Total.InputTokens, - OutputTokens: combo.Total.OutputTokens, - CachedTokens: combo.Total.CachedTokens, - CostUSD: totalCost, + RequestCount: combination.Total.RequestCount, + InputTokens: combination.Total.InputTokens, + OutputTokens: combination.Total.OutputTokens, + CachedTokens: combination.Total.CachedTokens, + CostUSD: combinationTotalCost, }, ByUser: make(map[string]UsageStatsJSON), } - for user, userStats := range combo.ByUser { - userCost := calculateCost(userStats, combo.Model) - result.Costs.ByUser[user] += userCost + for user, userStats := range combination.ByUser { + userCost := calculateCost(userStats, combination.Model, combination.ServiceTier) + if aggregateUserCosts != nil { + aggregateUserCosts[user] += userCost + } - comboJSON.ByUser[user] = UsageStatsJSON{ + combinationJSON.ByUser[user] = UsageStatsJSON{ RequestCount: userStats.RequestCount, InputTokens: userStats.InputTokens, OutputTokens: userStats.OutputTokens, @@ -281,12 +822,80 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { } } - result.Combinations[i] = comboJSON + result[index] = combinationJSON + } + + return result, roundCost(totalCost) +} + +func formatUTCOffsetLabel(timestamp time.Time) string { + _, offsetSeconds := timestamp.Zone() + sign := "+" + if offsetSeconds < 0 { + sign = "-" + offsetSeconds = -offsetSeconds + } + offsetHours := offsetSeconds / 3600 + offsetMinutes := (offsetSeconds % 3600) / 60 + if offsetMinutes == 0 { + return fmt.Sprintf("UTC%s%d", sign, offsetHours) + } + return fmt.Sprintf("UTC%s%d:%02d", sign, offsetHours, offsetMinutes) +} + +func formatWeekStartKey(cycleStartAt time.Time) string { + localCycleStart := cycleStartAt.In(time.Local) + return fmt.Sprintf("%s %s", localCycleStart.Format("2006-01-02 15:04:05"), formatUTCOffsetLabel(localCycleStart)) +} + +func buildByWeekCost(combinations []CostCombination) map[string]float64 { + byWeek := make(map[string]float64) + for _, combination := range combinations { + if combination.WeekStartUnix <= 0 { + continue + } + weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC() + weekKey := formatWeekStartKey(weekStartAt) + byWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ServiceTier) + } + for weekKey, weekCost := range byWeek { + byWeek[weekKey] = roundCost(weekCost) + } + return byWeek +} + +func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 { + if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() { + return 0 + } + windowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute + return cycleHint.ResetAt.UTC().Add(-windowDuration).Unix() +} + +func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { + u.mutex.Lock() + defer u.mutex.Unlock() + + result := &AggregatedUsageJSON{ + LastUpdated: u.LastUpdated, + Costs: CostsSummaryJSON{ + TotalUSD: 0, + ByUser: make(map[string]float64), + ByWeek: make(map[string]float64), + }, + } + + globalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser) + result.Combinations = globalCombinationsJSON + result.Costs.TotalUSD = totalCost + result.Costs.ByWeek = buildByWeekCost(u.Combinations) + + if len(result.Costs.ByWeek) == 0 { + result.Costs.ByWeek = nil } - result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100 for user, cost := range result.Costs.ByUser { - result.Costs.ByUser[user] = math.Round(cost*100) / 100 + result.Costs.ByUser[user] = roundCost(cost) } return result @@ -296,6 +905,9 @@ func (u *AggregatedUsage) Load() error { u.mutex.Lock() defer u.mutex.Unlock() + u.LastUpdated = time.Time{} + u.Combinations = nil + data, err := os.ReadFile(u.filePath) if err != nil { if os.IsNotExist(err) { @@ -316,12 +928,7 @@ func (u *AggregatedUsage) Load() error { u.LastUpdated = temp.LastUpdated u.Combinations = temp.Combinations - - for i := range u.Combinations { - if u.Combinations[i].ByUser == nil { - u.Combinations[i].ByUser = make(map[string]UsageStats) - } - } + normalizeCombinations(u.Combinations) return nil } @@ -349,47 +956,27 @@ func (u *AggregatedUsage) Save() error { return err } -func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, user string) error { +func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string) error { + return u.AddUsageWithCycleHint(model, inputTokens, outputTokens, cachedTokens, serviceTier, user, time.Now(), nil) +} + +func (u *AggregatedUsage) AddUsageWithCycleHint(model string, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string, observedAt time.Time, cycleHint *WeeklyCycleHint) error { if model == "" { return E.New("model cannot be empty") } + normalizedServiceTier := normalizeServiceTier(serviceTier) + if observedAt.IsZero() { + observedAt = time.Now() + } + u.mutex.Lock() defer u.mutex.Unlock() - u.LastUpdated = time.Now() + u.LastUpdated = observedAt + weekStartUnix := deriveWeekStartUnix(cycleHint) - var combo *CostCombination - for i := range u.Combinations { - if u.Combinations[i].Model == model { - combo = &u.Combinations[i] - break - } - } - - if combo == nil { - newCombo := CostCombination{ - Model: model, - Total: UsageStats{}, - ByUser: make(map[string]UsageStats), - } - u.Combinations = append(u.Combinations, newCombo) - combo = &u.Combinations[len(u.Combinations)-1] - } - - combo.Total.RequestCount++ - combo.Total.InputTokens += inputTokens - combo.Total.OutputTokens += outputTokens - combo.Total.CachedTokens += cachedTokens - - if user != "" { - userStats := combo.ByUser[user] - userStats.RequestCount++ - userStats.InputTokens += inputTokens - userStats.OutputTokens += outputTokens - userStats.CachedTokens += cachedTokens - combo.ByUser[user] = userStats - } + addUsageToCombinations(&u.Combinations, model, normalizedServiceTier, weekStartUnix, user, inputTokens, outputTokens, cachedTokens) go u.scheduleSave() From cf4791f1ad90d135b68bbc3957f731690da92793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 26 Feb 2026 12:54:00 +0800 Subject: [PATCH 154/185] platform: Improve iOS OOM killer --- .github/CRONET_GO_VERSION | 2 +- adapter/connections.go | 4 + box.go | 5 +- cmd/internal/build_libbox/main.go | 2 +- common/conntrack/conn.go | 54 ---------- common/conntrack/killer.go | 35 ------- common/conntrack/packet_conn.go | 55 ---------- common/conntrack/track.go | 47 --------- common/conntrack/track_disable.go | 5 - common/conntrack/track_enable.go | 5 - common/dialer/default.go | 24 +++-- constant/proxy.go | 1 + daemon/instance.go | 13 +++ daemon/started_service.go | 15 ++- debug.go | 8 +- experimental/libbox/command_server.go | 1 + experimental/libbox/ffi.json | 4 - experimental/libbox/memory.go | 19 ++-- go.mod | 62 ++++++------ go.sum | 124 +++++++++++------------ include/oom_killer.go | 10 ++ include/registry.go | 1 + option/oom_killer.go | 3 + protocol/naive/outbound.go | 4 + route/conn.go | 124 ++++++++++++++++++----- route/network.go | 7 +- route/route.go | 4 - service/oomkiller/service.go | 138 ++++++++++++++++++++++++++ service/oomkiller/service_stub.go | 39 ++++++++ 29 files changed, 458 insertions(+), 357 deletions(-) delete mode 100644 common/conntrack/conn.go delete mode 100644 common/conntrack/killer.go delete mode 100644 common/conntrack/packet_conn.go delete mode 100644 common/conntrack/track.go delete mode 100644 common/conntrack/track_disable.go delete mode 100644 common/conntrack/track_enable.go create mode 100644 include/oom_killer.go create mode 100644 option/oom_killer.go create mode 100644 service/oomkiller/service.go create mode 100644 service/oomkiller/service_stub.go diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index a703eff5..52565262 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -abd78bb191a815236485ad929716845ffb41465a +34ec1a064c64f274c4e70bf7a9c7de4bb12331f6 diff --git a/adapter/connections.go b/adapter/connections.go index 0682d05a..a0b9c0ef 100644 --- a/adapter/connections.go +++ b/adapter/connections.go @@ -9,6 +9,10 @@ import ( type ConnectionManager interface { Lifecycle + Count() int + CloseAll() + TrackConn(conn net.Conn) net.Conn + TrackPacketConn(conn net.PacketConn) net.PacketConn NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) } diff --git a/box.go b/box.go index 7885b0d4..fe116b31 100644 --- a/box.go +++ b/box.go @@ -125,7 +125,10 @@ func New(options Options) (*Box, error) { ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) - applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) + err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) + if err != nil { + return nil, err + } var needCacheFile bool var needClashAPI bool var needV2RayAPI bool diff --git a/cmd/internal/build_libbox/main.go b/cmd/internal/build_libbox/main.go index e9cbb1ef..c1282169 100644 --- a/cmd/internal/build_libbox/main.go +++ b/cmd/internal/build_libbox/main.go @@ -63,7 +63,7 @@ func init() { sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0") debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0") - sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0") + sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0") darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace") // memcTags = append(memcTags, "with_tailscale") sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird") diff --git a/common/conntrack/conn.go b/common/conntrack/conn.go deleted file mode 100644 index 4773d6a8..00000000 --- a/common/conntrack/conn.go +++ /dev/null @@ -1,54 +0,0 @@ -package conntrack - -import ( - "io" - "net" - - "github.com/sagernet/sing/common/x/list" -) - -type Conn struct { - net.Conn - element *list.Element[io.Closer] -} - -func NewConn(conn net.Conn) (net.Conn, error) { - connAccess.Lock() - element := openConnection.PushBack(conn) - connAccess.Unlock() - if KillerEnabled { - err := KillerCheck() - if err != nil { - conn.Close() - return nil, err - } - } - return &Conn{ - Conn: conn, - element: element, - }, nil -} - -func (c *Conn) Close() error { - if c.element.Value != nil { - connAccess.Lock() - if c.element.Value != nil { - openConnection.Remove(c.element) - c.element.Value = nil - } - connAccess.Unlock() - } - return c.Conn.Close() -} - -func (c *Conn) Upstream() any { - return c.Conn -} - -func (c *Conn) ReaderReplaceable() bool { - return true -} - -func (c *Conn) WriterReplaceable() bool { - return true -} diff --git a/common/conntrack/killer.go b/common/conntrack/killer.go deleted file mode 100644 index e0a71e5c..00000000 --- a/common/conntrack/killer.go +++ /dev/null @@ -1,35 +0,0 @@ -package conntrack - -import ( - runtimeDebug "runtime/debug" - "time" - - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/memory" -) - -var ( - KillerEnabled bool - MemoryLimit uint64 - killerLastCheck time.Time -) - -func KillerCheck() error { - if !KillerEnabled { - return nil - } - nowTime := time.Now() - if nowTime.Sub(killerLastCheck) < 3*time.Second { - return nil - } - killerLastCheck = nowTime - if memory.Total() > MemoryLimit { - Close() - go func() { - time.Sleep(time.Second) - runtimeDebug.FreeOSMemory() - }() - return E.New("out of memory") - } - return nil -} diff --git a/common/conntrack/packet_conn.go b/common/conntrack/packet_conn.go deleted file mode 100644 index c7274637..00000000 --- a/common/conntrack/packet_conn.go +++ /dev/null @@ -1,55 +0,0 @@ -package conntrack - -import ( - "io" - "net" - - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/x/list" -) - -type PacketConn struct { - net.PacketConn - element *list.Element[io.Closer] -} - -func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) { - connAccess.Lock() - element := openConnection.PushBack(conn) - connAccess.Unlock() - if KillerEnabled { - err := KillerCheck() - if err != nil { - conn.Close() - return nil, err - } - } - return &PacketConn{ - PacketConn: conn, - element: element, - }, nil -} - -func (c *PacketConn) Close() error { - if c.element.Value != nil { - connAccess.Lock() - if c.element.Value != nil { - openConnection.Remove(c.element) - c.element.Value = nil - } - connAccess.Unlock() - } - return c.PacketConn.Close() -} - -func (c *PacketConn) Upstream() any { - return bufio.NewPacketConn(c.PacketConn) -} - -func (c *PacketConn) ReaderReplaceable() bool { - return true -} - -func (c *PacketConn) WriterReplaceable() bool { - return true -} diff --git a/common/conntrack/track.go b/common/conntrack/track.go deleted file mode 100644 index 2c3e328b..00000000 --- a/common/conntrack/track.go +++ /dev/null @@ -1,47 +0,0 @@ -package conntrack - -import ( - "io" - "sync" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/x/list" -) - -var ( - connAccess sync.RWMutex - openConnection list.List[io.Closer] -) - -func Count() int { - if !Enabled { - return 0 - } - return openConnection.Len() -} - -func List() []io.Closer { - if !Enabled { - return nil - } - connAccess.RLock() - defer connAccess.RUnlock() - connList := make([]io.Closer, 0, openConnection.Len()) - for element := openConnection.Front(); element != nil; element = element.Next() { - connList = append(connList, element.Value) - } - return connList -} - -func Close() { - if !Enabled { - return - } - connAccess.Lock() - defer connAccess.Unlock() - for element := openConnection.Front(); element != nil; element = element.Next() { - common.Close(element.Value) - element.Value = nil - } - openConnection.Init() -} diff --git a/common/conntrack/track_disable.go b/common/conntrack/track_disable.go deleted file mode 100644 index 174d8b6e..00000000 --- a/common/conntrack/track_disable.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build !with_conntrack - -package conntrack - -const Enabled = false diff --git a/common/conntrack/track_enable.go b/common/conntrack/track_enable.go deleted file mode 100644 index a4bf9986..00000000 --- a/common/conntrack/track_enable.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build with_conntrack - -package conntrack - -const Enabled = true diff --git a/common/dialer/default.go b/common/dialer/default.go index 3ab2e05a..ad37834c 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -9,7 +9,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/listener" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" @@ -37,6 +36,7 @@ type DefaultDialer struct { udpAddr4 string udpAddr6 string netns string + connectionManager adapter.ConnectionManager networkManager adapter.NetworkManager networkStrategy *C.NetworkStrategy defaultNetworkStrategy bool @@ -47,6 +47,7 @@ type DefaultDialer struct { } func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { + connectionManager := service.FromContext[adapter.ConnectionManager](ctx) networkManager := service.FromContext[adapter.NetworkManager](ctx) platformInterface := service.FromContext[adapter.PlatformInterface](ctx) @@ -206,6 +207,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial udpAddr4: udpAddr4, udpAddr6: udpAddr6, netns: options.NetNs, + connectionManager: connectionManager, networkManager: networkManager, networkStrategy: networkStrategy, defaultNetworkStrategy: defaultNetworkStrategy, @@ -238,7 +240,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address return nil, E.New("domain not resolved") } if d.networkStrategy == nil { - return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) { + return d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkUDP: if !address.IsIPv6() { @@ -303,12 +305,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin if !fastFallback && !isPrimary { d.networkLastFallback.Store(time.Now()) } - return trackConn(conn, nil) + return d.trackConn(conn, nil) } func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if d.networkStrategy == nil { - return trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) { + return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) { if destination.IsIPv6() { return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6) } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { @@ -360,23 +362,23 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina return nil, err } } - return trackPacketConn(packetConn, nil) + return d.trackPacketConn(packetConn, nil) } func (d *DefaultDialer) WireGuardControl() control.Func { return d.udpListener.Control } -func trackConn(conn net.Conn, err error) (net.Conn, error) { - if !conntrack.Enabled || err != nil { +func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) { + if d.connectionManager == nil || err != nil { return conn, err } - return conntrack.NewConn(conn) + return d.connectionManager.TrackConn(conn), nil } -func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) { - if !conntrack.Enabled || err != nil { +func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) { + if d.connectionManager == nil || err != nil { return conn, err } - return conntrack.NewPacketConn(conn) + return d.connectionManager.TrackPacketConn(conn), nil } diff --git a/constant/proxy.go b/constant/proxy.go index a5193623..4130c631 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -30,6 +30,7 @@ const ( TypeSSMAPI = "ssm-api" TypeCCM = "ccm" TypeOCM = "ocm" + TypeOOMKiller = "oom-killer" ) const ( diff --git a/daemon/instance.go b/daemon/instance.go index 5947452f..4ed74182 100644 --- a/daemon/instance.go +++ b/daemon/instance.go @@ -7,10 +7,12 @@ import ( "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/service" @@ -21,6 +23,7 @@ type Instance struct { ctx context.Context cancel context.CancelFunc instance *box.Box + connectionManager adapter.ConnectionManager clashServer adapter.ClashServer cacheFile adapter.CacheFile pauseManager pause.Manager @@ -84,6 +87,15 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove } } } + if s.oomKiller && C.IsIos { + if !common.Any(options.Services, func(it option.Service) bool { + return it.Type == C.TypeOOMKiller + }) { + options.Services = append(options.Services, option.Service{ + Type: C.TypeOOMKiller, + }) + } + } urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) i := &Instance{ @@ -101,6 +113,7 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove return nil, err } i.instance = boxInstance + i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx) i.clashServer = service.FromContext[adapter.ClashServer](ctx) i.pauseManager = service.FromContext[pause.Manager](ctx) i.cacheFile = service.FromContext[adapter.CacheFile](ctx) diff --git a/daemon/started_service.go b/daemon/started_service.go index 7c6fa945..5e677f7a 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -8,7 +8,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/experimental/clashapi" "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" @@ -36,6 +35,7 @@ type StartedService struct { handler PlatformHandler debug bool logMaxLines int + oomKiller bool // workingDirectory string // tempDirectory string // userID int @@ -67,6 +67,7 @@ type ServiceOptions struct { Handler PlatformHandler Debug bool LogMaxLines int + OOMKiller bool // WorkingDirectory string // TempDirectory string // UserID int @@ -81,6 +82,7 @@ func NewStartedService(options ServiceOptions) *StartedService { handler: options.Handler, debug: options.Debug, logMaxLines: options.LogMaxLines, + oomKiller: options.OOMKiller, // workingDirectory: options.WorkingDirectory, // tempDirectory: options.TempDirectory, // userID: options.UserID, @@ -409,10 +411,12 @@ func (s *StartedService) readStatus() *Status { var status Status status.Memory = memory.Inuse() status.Goroutines = int32(runtime.NumGoroutine()) - status.ConnectionsOut = int32(conntrack.Count()) s.serviceAccess.RLock() nowService := s.instance s.serviceAccess.RUnlock() + if nowService != nil && nowService.connectionManager != nil { + status.ConnectionsOut = int32(nowService.connectionManager.Count()) + } if nowService != nil { if clashServer := nowService.clashServer; clashServer != nil { status.TrafficAvailable = true @@ -993,7 +997,12 @@ func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConn } func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { - conntrack.Close() + s.serviceAccess.RLock() + nowService := s.instance + s.serviceAccess.RUnlock() + if nowService != nil && nowService.connectionManager != nil { + nowService.connectionManager.CloseAll() + } return &emptypb.Empty{}, nil } diff --git a/debug.go b/debug.go index 1726c10e..f620172b 100644 --- a/debug.go +++ b/debug.go @@ -3,11 +3,11 @@ package box import ( "runtime/debug" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" ) -func applyDebugOptions(options option.DebugOptions) { +func applyDebugOptions(options option.DebugOptions) error { applyDebugListenOption(options) if options.GCPercent != nil { debug.SetGCPercent(*options.GCPercent) @@ -26,9 +26,9 @@ func applyDebugOptions(options option.DebugOptions) { } if options.MemoryLimit.Value() != 0 { debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5)) - conntrack.MemoryLimit = options.MemoryLimit.Value() } if options.OOMKiller != nil { - conntrack.KillerEnabled = *options.OOMKiller + return E.New("legacy oom_killer in debug options is removed, use oom-killer service instead") } + return nil } diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index e3300281..1c2412b6 100644 --- a/experimental/libbox/command_server.go +++ b/experimental/libbox/command_server.go @@ -60,6 +60,7 @@ func NewCommandServer(handler CommandServerHandler, platformInterface PlatformIn Handler: (*platformHandler)(server), Debug: sDebug, LogMaxLines: sLogMaxLines, + OOMKiller: memoryLimitEnabled, // WorkingDirectory: sWorkingPath, // TempDirectory: sTempPath, // UserID: sUserID, diff --git a/experimental/libbox/ffi.json b/experimental/libbox/ffi.json index 28333871..81fae27d 100644 --- a/experimental/libbox/ffi.json +++ b/experimental/libbox/ffi.json @@ -29,7 +29,6 @@ "with_utls", "with_naive_outbound", "with_clash_api", - "with_conntrack", "badlinkname", "tfogo_checklinkname0", "with_tailscale", @@ -59,7 +58,6 @@ "with_wireguard", "with_utls", "with_clash_api", - "with_conntrack", "badlinkname", "tfogo_checklinkname0", "with_tailscale", @@ -90,7 +88,6 @@ "with_utls", "with_naive_outbound", "with_clash_api", - "with_conntrack", "badlinkname", "tfogo_checklinkname0", "with_dhcp", @@ -134,7 +131,6 @@ "with_naive_outbound", "with_purego", "with_clash_api", - "with_conntrack", "badlinkname", "tfogo_checklinkname0", "with_tailscale", diff --git a/experimental/libbox/memory.go b/experimental/libbox/memory.go index b10c6701..b0b87f73 100644 --- a/experimental/libbox/memory.go +++ b/experimental/libbox/memory.go @@ -4,20 +4,23 @@ import ( "math" runtimeDebug "runtime/debug" - "github.com/sagernet/sing-box/common/conntrack" + C "github.com/sagernet/sing-box/constant" ) +var memoryLimitEnabled bool + func SetMemoryLimit(enabled bool) { - const memoryLimit = 45 * 1024 * 1024 - const memoryLimitGo = memoryLimit / 1.5 + memoryLimitEnabled = enabled + const memoryLimitGo = 45 * 1024 * 1024 if enabled { runtimeDebug.SetGCPercent(10) - runtimeDebug.SetMemoryLimit(memoryLimitGo) - conntrack.KillerEnabled = true - conntrack.MemoryLimit = memoryLimit + if C.IsIos { + runtimeDebug.SetMemoryLimit(memoryLimitGo) + } } else { runtimeDebug.SetGCPercent(100) - runtimeDebug.SetMemoryLimit(math.MaxInt64) - conntrack.KillerEnabled = false + if C.IsIos { + runtimeDebug.SetMemoryLimit(math.MaxInt64) + } } } diff --git a/go.mod b/go.mod index 0523b73f..506b54cf 100644 --- a/go.mod +++ b/go.mod @@ -27,8 +27,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8 - github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8 + github.com/sagernet/cronet-go v0.0.0-20260226034600-34ec1a064c64 + github.com/sagernet/cronet-go/all v0.0.0-20260226034600-34ec1a064c64 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -105,35 +105,35 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index 738a415b..c9478c27 100644 --- a/go.sum +++ b/go.sum @@ -152,68 +152,68 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8 h1:XcZiLUXnYE74RvqVdsyxgIInBuFaZbABx2Hom5U6uuk= -github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8 h1:uaUy9opPmPYD+viUeUnBzT+lw5b19j6pC/iKew7u13I= -github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8/go.mod h1:Gn1d0D8adjp7mlgSv+/pVLJsG+engIMBp/R4+1MOhlk= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe h1:iKIZJsvD+D3sdAzAeeOodJBxnFL9OVs1LTq3xnmQ6wQ= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:/YhWKKVb3uQ5JmBQwFEOKg8QK2w0Ky6dxEb/UHrhQww= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe h1:h+XF746wRtYKavUeS8//Vro6s9f0F6+pI8VQFLMLg6E= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:yMs96D9ErwAG8gEHV6zaQ5cp9ZPNBHExxJ5+u8cZ644= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:HUJtGjXcB+70W+YfeLgue6X1u69XLN0Ar56Ipg3gtvY= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:ivo7JwVqDTMf/qVfpKYdwcIc+NzKGyMJ/WLj/TTNYXg= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:HdWJLwa/Ie3jsueJ0O2mZd4V/NP1UJ6bamdcNHWsYEo= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:A9PWi2xCI+TCr9ALr+BO76WCCk1JnRyjeEH0/+rdyRc= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:aMOUWbGjkPBFqObA+uAJOfVuBkHfvz2sibNgOJqjuBs= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe h1:CzE+sJ2iOvJwOuZhpiDV5VlQrBaNJAZhDCafly+TH9c= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe h1:2grC2CeyUiYVgqG7BGKpJvjFzYv0wL64QMoBqOHVZsI= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:+N9/LauocInR5kxXU+L5bQe1bndCZUC+6L0FaozWZNI= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe h1:mRJcjGtKG/eaPL4sZ4Ij+e7aLdg1AEXNI1PgRnxI6H8= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe h1:UkWiTAxUAjTtsu7e52cvMrmbShz+ahTdGkhF9mEIIZU= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:giJVex0bwZy+DwmPwfZ+NZmafBRTsaZ+QUaD2Fkacxs= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe h1:XkjAQkciY78eSMF/9VdaWRWb+OfPvoIxVKx5gHGfSIg= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe h1:8uDfbPXAL0MWqGI8bm6YJghRmGvK08z4jEIGoODKqTI= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe h1:Ucs4htbATTdG7YGHCyQ4vMYRhltVvsapZ95THRNssr4= -github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= -github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe h1:oeQjTH4lveV4M7/hqOJFfwQ9UfWvkFZEXTc00R2acuk= -github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= -github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe h1:NWABhpSuXcN61hF0CUqwliJXxEbmHidoAHxtB61V3GA= -github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= -github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe h1:+0VrQdlGR/zLjPzinXFqFR2sdzF2BXoXu7f8xaMuwtg= -github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= -github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe h1:DYW55QJOZBI4Znjhc0IiusF+IMg4R2dHPX0KnZC6gSo= -github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= -github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe h1:xbbZtyXOxYJMplsyv371ddQb7QrEnyXIIGdUK/3WNTE= -github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= -github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe h1:YSH2lVT+Sn29lQQbwhDpxZvGjVSg80SUfW4JQ8vM3aA= -github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:gQ1veofYJr8Z1hBVM2PIrn4+EMKvwh+zWpYBr+mxgQ8= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:e2TMlbEottRCDfTWxUSw4Jl5dK8IInV02XIvLKVjLbM= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:Cgh+DP/Ns1djisz+LFxA1nEhyF6EEU5ZdVxNTkiX2BI= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:VCtjRmkI1IkKdWQ3Jh7j/ze5fhBQJZo1JR70cVKLaKw= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:SKePXZMEPUY5zA1VFBPbPOxZsfb/wkMNZAvjPO7hL+I= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20260226034600-34ec1a064c64 h1:2hcUvxlEbzs4hbt9l0oeBpUfet2PHzaV6X6zNTDMkvQ= +github.com/sagernet/cronet-go v0.0.0-20260226034600-34ec1a064c64/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260226034600-34ec1a064c64 h1:f5TU9VAbhtm/SMLKctjMiSNUoQso4S6kEPUm5yNglNo= +github.com/sagernet/cronet-go/all v0.0.0-20260226034600-34ec1a064c64/go.mod h1:ohbVnfJvBzdv7R87Nz0fAmkLSTF9lCKrQC2rIqsQPY4= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260226033953-5745aabe7717 h1:vtE5zVWFQuNls2jHb5edFCFYJblGY9qrqTVpoW2V/h4= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260226033953-5745aabe7717/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260226033953-5745aabe7717 h1:+d5efMdtHbizXb4Vuai/11uNpWXmwOCfVgQcICA3kgk= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260226033953-5745aabe7717 h1:6BYObS6w5sWnNLChKT1fOYJnJt/+p6du8fSyDb3DLwo= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260226033953-5745aabe7717/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260226033953-5745aabe7717 h1:UbgjfMai+duPsftAT94zr4KcnACuJhjaxQkpup6TWLk= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260226033953-5745aabe7717 h1:KRrzFzsWWgy8XPHfp2hEX7wTuc7ILQZiFKIATvuyCHU= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260226033953-5745aabe7717 h1:bTp8j/GqUsS0P2aaTcbs7alSvUoywx+YIWyoCcVA05I= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260226033953-5745aabe7717 h1:PKuCYfGpSfnzgRJjRR5em/yU5SoI4Yvx3r0C2k1Ksh4= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260226033953-5745aabe7717 h1:w5ytvTm42ncUIFbLueN/ilp2ZaHrr5GC76Kc/Oxw4qI= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260226033953-5745aabe7717 h1:2BjQH02cCF6spz/WG8bJHBPhZtF4Or5D73L+n5fZbNE= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260226033953-5745aabe7717 h1:N9KUwDODj5eP5uqdSmbMufml2nGPc7oX4H5NPAjADIs= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260226033953-5745aabe7717/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260226033953-5745aabe7717 h1:v2p6tqpQVI6RR1HamuJmkz01Ght4miQ79YhzQtOYowk= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260226033953-5745aabe7717 h1:glPUIWUUAlOII+L0UI7/JAJ8o0wqAT7qVmWB4AcPyWs= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260226033953-5745aabe7717 h1:0QKgSc5nBrR6H1ImsOlaBbVQ7onoFhwxTBwiOnUn0es= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260226033953-5745aabe7717 h1:DsrfMoWf05lLrwdo0s/6wAC/4smeDgT4+t/iJtwGD+g= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260226033953-5745aabe7717/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260226033953-5745aabe7717 h1:bYqI37NWgvxVBeWL50/ts23uGiyHv7QfH/Ylu5lvgLs= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260226033953-5745aabe7717 h1:zg8keyQA2sniD4gqMwbUVdf57M1cmGobumOtZy1CYJs= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260226033953-5745aabe7717 h1:cgyVkTyccMGiCayGgcvfc+q0b5GdhqSk+3UMmGhlCi0= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260226033953-5745aabe7717 h1:anuE3B73oIvDOGJGvenfkQTm3NY2vL84aM062t8DZ4Y= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260226033953-5745aabe7717 h1:kYN/amWzLOZMm5evPwlmumnswBNPeFZiKINKP73AsuA= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260226033953-5745aabe7717 h1:S7rlC1Knzs3+It2u6AnhGUgiLU1QUzvoV3JXWvwE4n8= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260226033953-5745aabe7717/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260226033953-5745aabe7717 h1:FeyB5ZyFHAWkU1DETmUjLEM9GUFWoIndV8usT4AjGOk= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260226033953-5745aabe7717/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260226033953-5745aabe7717 h1:loU7PsyqI7dplbFGjQKoMT4IVYuzVyfW88FIq+7gbmY= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260226033953-5745aabe7717 h1:XSVrZ18XauiYMf+G0CzZETteQF0r1NOoA1gVXlZWjUI= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260226033953-5745aabe7717 h1:oTRK4T5pQ1ZYQh7Avr5u3bsDnXkloSVqwBljRy8FHOY= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260226033953-5745aabe7717 h1:J18xRmUa9lle+Y6pYkQ7OMq2B9eox5Bmm4MkbuQPQm0= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260226033953-5745aabe7717 h1:PC9gKeDN3wmcFxwiBEM57W0YwEz1uFGqawwssEQe494= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260226033953-5745aabe7717 h1:STlbMnsydaymby26m3wNfeYYI7HoiZ6+wxQ75Si1g8E= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260226033953-5745aabe7717 h1:qWtpoBxmHyb49IRtMAFkJIRlL4KCYW5Fe6YptYhItpM= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe7717 h1:7ohuWJ0PswrU7Xw/xfL6OrIh7sXybGNOgh6DaL+urdY= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.11 h1:niMQAspvuThup5eRZQpsGcbM76zAvnsGr7RUIpnQMDQ= diff --git a/include/oom_killer.go b/include/oom_killer.go new file mode 100644 index 00000000..3f70d9d0 --- /dev/null +++ b/include/oom_killer.go @@ -0,0 +1,10 @@ +package include + +import ( + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/service/oomkiller" +) + +func registerOOMKillerService(registry *service.Registry) { + oomkiller.RegisterService(registry) +} diff --git a/include/registry.go b/include/registry.go index d909b850..64a49b61 100644 --- a/include/registry.go +++ b/include/registry.go @@ -137,6 +137,7 @@ func ServiceRegistry() *service.Registry { registerDERPService(registry) registerCCMService(registry) registerOCMService(registry) + registerOOMKillerService(registry) return registry } diff --git a/option/oom_killer.go b/option/oom_killer.go new file mode 100644 index 00000000..9fbbde84 --- /dev/null +++ b/option/oom_killer.go @@ -0,0 +1,3 @@ +package option + +type OOMKillerServiceOptions struct{} diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index dcc1aec5..8249a1fe 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -254,6 +254,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return h.uotClient.ListenPacket(ctx, destination) } +func (h *Outbound) InterfaceUpdated() { + h.client.Engine().CloseAllConnections() +} + func (h *Outbound) Close() error { return h.client.Close() } diff --git a/route/conn.go b/route/conn.go index 3e9c831c..899d2939 100644 --- a/route/conn.go +++ b/route/conn.go @@ -44,16 +44,52 @@ func (m *ConnectionManager) Start(stage adapter.StartStage) error { return nil } -func (m *ConnectionManager) Close() error { +func (m *ConnectionManager) Count() int { + return m.connections.Len() +} + +func (m *ConnectionManager) CloseAll() { m.access.Lock() - defer m.access.Unlock() - for element := m.connections.Front(); element != nil; element = element.Next() { - common.Close(element.Value) + var closers []io.Closer + for element := m.connections.Front(); element != nil; { + nextElement := element.Next() + closers = append(closers, element.Value) + m.connections.Remove(element) + element = nextElement } - m.connections.Init() + m.access.Unlock() + for _, closer := range closers { + common.Close(closer) + } +} + +func (m *ConnectionManager) Close() error { + m.CloseAll() return nil } +func (m *ConnectionManager) TrackConn(conn net.Conn) net.Conn { + m.access.Lock() + element := m.connections.PushBack(conn) + m.access.Unlock() + return &trackedConn{ + Conn: conn, + manager: m, + element: element, + } +} + +func (m *ConnectionManager) TrackPacketConn(conn net.PacketConn) net.PacketConn { + m.access.Lock() + element := m.connections.PushBack(conn) + m.access.Unlock() + return &trackedPacketConn{ + PacketConn: conn, + manager: m, + element: element, + } +} + func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ctx = adapter.WithContext(ctx, &metadata) var ( @@ -92,14 +128,6 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co if metadata.TLSFragment || metadata.TLSRecordFragment { remoteConn = tf.NewConn(remoteConn, ctx, metadata.TLSFragment, metadata.TLSRecordFragment, metadata.TLSFragmentFallbackDelay) } - m.access.Lock() - element := m.connections.PushBack(conn) - m.access.Unlock() - onClose = N.AppendClose(onClose, func(it error) { - m.access.Lock() - defer m.access.Unlock() - m.connections.Remove(element) - }) var done atomic.Bool if m.kickWriteHandshake(ctx, conn, remoteConn, false, &done, onClose) { return @@ -216,14 +244,6 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial ctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout) } destination := bufio.NewPacketConn(remotePacketConn) - m.access.Lock() - element := m.connections.PushBack(conn) - m.access.Unlock() - onClose = N.AppendClose(onClose, func(it error) { - m.access.Lock() - defer m.access.Unlock() - m.connections.Remove(element) - }) var done atomic.Bool go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose) go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose) @@ -242,7 +262,9 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn, destination.Close() } if done.Swap(true) { - onClose(err) + if onClose != nil { + onClose(err) + } common.Close(source, destination) } if !direction { @@ -303,7 +325,9 @@ func (m *ConnectionManager) kickWriteHandshake(ctx context.Context, source net.C return false } if !done.Swap(true) { - onClose(err) + if onClose != nil { + onClose(err) + } } common.Close(source, destination) if !direction { @@ -334,7 +358,59 @@ func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.P } } if !done.Swap(true) { - onClose(err) + if onClose != nil { + onClose(err) + } } common.Close(source, destination) } + +type trackedConn struct { + net.Conn + manager *ConnectionManager + element *list.Element[io.Closer] +} + +func (c *trackedConn) Close() error { + c.manager.access.Lock() + c.manager.connections.Remove(c.element) + c.manager.access.Unlock() + return c.Conn.Close() +} + +func (c *trackedConn) Upstream() any { + return c.Conn +} + +func (c *trackedConn) ReaderReplaceable() bool { + return true +} + +func (c *trackedConn) WriterReplaceable() bool { + return true +} + +type trackedPacketConn struct { + net.PacketConn + manager *ConnectionManager + element *list.Element[io.Closer] +} + +func (c *trackedPacketConn) Close() error { + c.manager.access.Lock() + c.manager.connections.Remove(c.element) + c.manager.access.Unlock() + return c.PacketConn.Close() +} + +func (c *trackedPacketConn) Upstream() any { + return bufio.NewPacketConn(c.PacketConn) +} + +func (c *trackedPacketConn) ReaderReplaceable() bool { + return true +} + +func (c *trackedPacketConn) WriterReplaceable() bool { + return true +} diff --git a/route/network.go b/route/network.go index b53142b5..b8eefdc0 100644 --- a/route/network.go +++ b/route/network.go @@ -13,7 +13,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/settings" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" @@ -48,6 +47,7 @@ type NetworkManager struct { powerListener winpowrprof.EventListener pauseManager pause.Manager platformInterface adapter.PlatformInterface + connectionManager adapter.ConnectionManager endpoint adapter.EndpointManager inbound adapter.InboundManager outbound adapter.OutboundManager @@ -90,6 +90,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options }, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[adapter.PlatformInterface](ctx), + connectionManager: service.FromContext[adapter.ConnectionManager](ctx), endpoint: service.FromContext[adapter.EndpointManager](ctx), inbound: service.FromContext[adapter.InboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx), @@ -450,7 +451,9 @@ func (r *NetworkManager) UpdateWIFIState() { } func (r *NetworkManager) ResetNetwork() { - conntrack.Close() + if r.connectionManager != nil { + r.connectionManager.CloseAll() + } for _, endpoint := range r.endpoint.Endpoints() { listener, isListener := endpoint.(adapter.InterfaceUpdateListener) diff --git a/route/route.go b/route/route.go index fd025a1b..240d0343 100644 --- a/route/route.go +++ b/route/route.go @@ -9,7 +9,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" @@ -80,7 +79,6 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad injectable.NewConnectionEx(ctx, conn, metadata, onClose) return nil } - conntrack.KillerCheck() metadata.Network = N.NetworkTCP switch metadata.Destination.Fqdn { case mux.Destination.Fqdn: @@ -216,8 +214,6 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m injectable.NewPacketConnectionEx(ctx, conn, metadata, onClose) return nil } - conntrack.KillerCheck() - // TODO: move to UoT metadata.Network = N.NetworkUDP diff --git a/service/oomkiller/service.go b/service/oomkiller/service.go new file mode 100644 index 00000000..fb486ab9 --- /dev/null +++ b/service/oomkiller/service.go @@ -0,0 +1,138 @@ +//go:build darwin && cgo + +package oomkiller + +/* +#include + +static dispatch_source_t memoryPressureSource; + +extern void goMemoryPressureCallback(unsigned long status); + +static void startMemoryPressureMonitor() { + memoryPressureSource = dispatch_source_create( + DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, + 0, + DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL, + dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) + ); + dispatch_source_set_event_handler(memoryPressureSource, ^{ + unsigned long status = dispatch_source_get_data(memoryPressureSource); + goMemoryPressureCallback(status); + }); + dispatch_activate(memoryPressureSource); +} + +static void stopMemoryPressureMonitor() { + if (memoryPressureSource) { + dispatch_source_cancel(memoryPressureSource); + memoryPressureSource = NULL; + } +} +*/ +import "C" + +import ( + "context" + runtimeDebug "runtime/debug" + "sync" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + boxConstant "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/memory" + "github.com/sagernet/sing/service" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService) +} + +var ( + globalAccess sync.Mutex + globalServices []*Service +) + +type Service struct { + boxService.Adapter + logger log.ContextLogger + router adapter.Router +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) { + return &Service{ + Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag), + logger: logger, + router: service.FromContext[adapter.Router](ctx), + }, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + globalAccess.Lock() + isFirst := len(globalServices) == 0 + globalServices = append(globalServices, s) + globalAccess.Unlock() + if isFirst { + C.startMemoryPressureMonitor() + } + s.logger.Info("started memory pressure monitor") + return nil +} + +func (s *Service) Close() error { + globalAccess.Lock() + for i, service := range globalServices { + if service == s { + globalServices = append(globalServices[:i], globalServices[i+1:]...) + break + } + } + isLast := len(globalServices) == 0 + globalAccess.Unlock() + if isLast { + C.stopMemoryPressureMonitor() + } + return nil +} + +//export goMemoryPressureCallback +func goMemoryPressureCallback(status C.ulong) { + globalAccess.Lock() + services := make([]*Service, len(globalServices)) + copy(services, globalServices) + globalAccess.Unlock() + if len(services) == 0 { + return + } + criticalFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_CRITICAL) + warnFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_WARN) + isCritical := status&criticalFlag != 0 + isWarning := status&warnFlag != 0 + var level string + switch { + case isCritical: + level = "critical" + case isWarning: + level = "warning" + default: + level = "normal" + } + for _, s := range services { + if isCritical { + s.logger.Error("memory pressure: ", level, ", usage: ", memory.Total()/(1024*1024), " MiB, resetting network") + s.router.ResetNetwork() + } else if isWarning { + s.logger.Warn("memory pressure: ", level, ", usage: ", memory.Total()/(1024*1024), " MiB") + } else { + s.logger.Debug("memory pressure: ", level, ", usage: ", memory.Total()/(1024*1024), " MiB") + } + } + if isCritical { + runtimeDebug.FreeOSMemory() + } +} diff --git a/service/oomkiller/service_stub.go b/service/oomkiller/service_stub.go new file mode 100644 index 00000000..425f525e --- /dev/null +++ b/service/oomkiller/service_stub.go @@ -0,0 +1,39 @@ +//go:build !darwin || !cgo + +package oomkiller + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.OOMKillerServiceOptions](registry, C.TypeOOMKiller, NewService) +} + +type Service struct { + boxService.Adapter +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) { + return &Service{ + Adapter: boxService.NewAdapter(C.TypeOOMKiller, tag), + }, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + return E.New("memory pressure monitoring is not available on this platform") +} + +func (s *Service) Close() error { + return nil +} From 21a1512e6c6faa141ad9a043671562e54375fda8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 26 Feb 2026 21:45:11 +0800 Subject: [PATCH 155/185] tailscale: Fix AdvertiseTags --- protocol/tailscale/endpoint.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 40bc4bc6..659277d9 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -210,10 +210,11 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL UserLogf: func(format string, args ...any) { logger.Debug(fmt.Sprintf(format, args...)) }, - Ephemeral: options.Ephemeral, - AuthKey: options.AuthKey, - ControlURL: options.ControlURL, - Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger}, + Ephemeral: options.Ephemeral, + AuthKey: options.AuthKey, + ControlURL: options.ControlURL, + AdvertiseTags: options.AdvertiseTags, + Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger}, LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) { return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions()) }, @@ -363,12 +364,10 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { Prefs: ipn.Prefs{ RouteAll: t.acceptRoutes, AdvertiseRoutes: t.advertiseRoutes, - AdvertiseTags: t.advertiseTags, }, RouteAllSet: true, ExitNodeIPSet: true, AdvertiseRoutesSet: true, - AdvertiseTagsSet: true, RelayServerPortSet: true, RelayServerStaticEndpointsSet: true, } From 65150f5cc3d5ee593e194a8536c952bb1d47642a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 27 Feb 2026 13:35:58 +0800 Subject: [PATCH 156/185] platform: Improve OOM killer for iOS --- daemon/started_service.go | 2 +- go.mod | 2 +- go.sum | 4 +- option/oom_killer.go | 13 ++- service/oomkiller/config.go | 51 ++++++++++ service/oomkiller/service.go | 82 ++++++++++++--- service/oomkiller/service_stub.go | 54 ++++++++-- service/oomkiller/service_timer.go | 158 +++++++++++++++++++++++++++++ 8 files changed, 341 insertions(+), 25 deletions(-) create mode 100644 service/oomkiller/config.go create mode 100644 service/oomkiller/service_timer.go diff --git a/daemon/started_service.go b/daemon/started_service.go index 5e677f7a..7ebdac1e 100644 --- a/daemon/started_service.go +++ b/daemon/started_service.go @@ -409,7 +409,7 @@ func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server func (s *StartedService) readStatus() *Status { var status Status - status.Memory = memory.Inuse() + status.Memory = memory.Total() status.Goroutines = int32(runtime.NumGoroutine()) s.serviceAccess.RLock() nowService := s.instance diff --git a/go.mod b/go.mod index 506b54cf..37724e7e 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.11 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 - github.com/sagernet/sing v0.8.0-beta.16 + github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.13 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index c9478c27..8c683a95 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA= -github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07 h1:LQqb+xtR5uqF6bePmJQ3sAToF/kMCjxSnz17HnboXA8= +github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0-beta.13 h1:umDr6GC5fVbOIoTvqV4544wY61zEN+ObQwVGNP8sX1M= diff --git a/option/oom_killer.go b/option/oom_killer.go index 9fbbde84..2032ed09 100644 --- a/option/oom_killer.go +++ b/option/oom_killer.go @@ -1,3 +1,14 @@ package option -type OOMKillerServiceOptions struct{} +import ( + "github.com/sagernet/sing/common/byteformats" + "github.com/sagernet/sing/common/json/badoption" +) + +type OOMKillerServiceOptions struct { + MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"` + SafetyMargin *byteformats.MemoryBytes `json:"safety_margin,omitempty"` + MinInterval badoption.Duration `json:"min_interval,omitempty"` + MaxInterval badoption.Duration `json:"max_interval,omitempty"` + ChecksBeforeLimit int `json:"checks_before_limit,omitempty"` +} diff --git a/service/oomkiller/config.go b/service/oomkiller/config.go new file mode 100644 index 00000000..693ced99 --- /dev/null +++ b/service/oomkiller/config.go @@ -0,0 +1,51 @@ +package oomkiller + +import ( + "time" + + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func buildTimerConfig(options option.OOMKillerServiceOptions, memoryLimit uint64, useAvailable bool) (timerConfig, error) { + safetyMargin := uint64(defaultSafetyMargin) + if options.SafetyMargin != nil && options.SafetyMargin.Value() > 0 { + safetyMargin = options.SafetyMargin.Value() + } + + minInterval := defaultMinInterval + if options.MinInterval != 0 { + minInterval = time.Duration(options.MinInterval.Build()) + if minInterval <= 0 { + return timerConfig{}, E.New("min_interval must be greater than 0") + } + } + + maxInterval := defaultMaxInterval + if options.MaxInterval != 0 { + maxInterval = time.Duration(options.MaxInterval.Build()) + if maxInterval <= 0 { + return timerConfig{}, E.New("max_interval must be greater than 0") + } + } + if maxInterval < minInterval { + return timerConfig{}, E.New("max_interval must be greater than or equal to min_interval") + } + + checksBeforeLimit := defaultChecksBeforeLimit + if options.ChecksBeforeLimit != 0 { + checksBeforeLimit = options.ChecksBeforeLimit + if checksBeforeLimit <= 0 { + return timerConfig{}, E.New("checks_before_limit must be greater than 0") + } + } + + return timerConfig{ + memoryLimit: memoryLimit, + safetyMargin: safetyMargin, + minInterval: minInterval, + maxInterval: maxInterval, + checksBeforeLimit: checksBeforeLimit, + useAvailable: useAvailable, + }, nil +} diff --git a/service/oomkiller/service.go b/service/oomkiller/service.go index fb486ab9..c3612d92 100644 --- a/service/oomkiller/service.go +++ b/service/oomkiller/service.go @@ -57,37 +57,72 @@ var ( type Service struct { boxService.Adapter - logger log.ContextLogger - router adapter.Router + logger log.ContextLogger + router adapter.Router + memoryLimit uint64 + hasTimerMode bool + useAvailable bool + timerConfig timerConfig + adaptiveTimer *adaptiveTimer } func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) { - return &Service{ + s := &Service{ Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag), logger: logger, router: service.FromContext[adapter.Router](ctx), - }, nil + } + + if options.MemoryLimit != nil { + s.memoryLimit = options.MemoryLimit.Value() + if s.memoryLimit > 0 { + s.hasTimerMode = true + } + } + + config, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable) + if err != nil { + return nil, err + } + s.timerConfig = config + + return s, nil } func (s *Service) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } + + if s.hasTimerMode { + s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig) + if s.memoryLimit > 0 { + s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB") + } else { + s.logger.Info("started memory monitor with available memory detection") + } + } else { + s.logger.Info("started memory pressure monitor") + } + globalAccess.Lock() isFirst := len(globalServices) == 0 globalServices = append(globalServices, s) globalAccess.Unlock() + if isFirst { C.startMemoryPressureMonitor() } - s.logger.Info("started memory pressure monitor") return nil } func (s *Service) Close() error { + if s.adaptiveTimer != nil { + s.adaptiveTimer.stop() + } globalAccess.Lock() - for i, service := range globalServices { - if service == s { + for i, svc := range globalServices { + if svc == s { globalServices = append(globalServices[:i], globalServices[i+1:]...) break } @@ -122,17 +157,36 @@ func goMemoryPressureCallback(status C.ulong) { default: level = "normal" } + var freeOSMemory bool for _, s := range services { - if isCritical { - s.logger.Error("memory pressure: ", level, ", usage: ", memory.Total()/(1024*1024), " MiB, resetting network") - s.router.ResetNetwork() - } else if isWarning { - s.logger.Warn("memory pressure: ", level, ", usage: ", memory.Total()/(1024*1024), " MiB") + usage := memory.Total() + if s.hasTimerMode { + if isCritical { + s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB") + if s.adaptiveTimer != nil { + s.adaptiveTimer.startNow() + } + } else if isWarning { + s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB") + } else { + s.logger.Debug("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB") + if s.adaptiveTimer != nil { + s.adaptiveTimer.stop() + } + } } else { - s.logger.Debug("memory pressure: ", level, ", usage: ", memory.Total()/(1024*1024), " MiB") + if isCritical { + s.logger.Error("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB, resetting network") + s.router.ResetNetwork() + freeOSMemory = true + } else if isWarning { + s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB") + } else { + s.logger.Debug("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB") + } } } - if isCritical { + if freeOSMemory { runtimeDebug.FreeOSMemory() } } diff --git a/service/oomkiller/service_stub.go b/service/oomkiller/service_stub.go index 425f525e..13348bac 100644 --- a/service/oomkiller/service_stub.go +++ b/service/oomkiller/service_stub.go @@ -7,33 +7,75 @@ import ( "github.com/sagernet/sing-box/adapter" boxService "github.com/sagernet/sing-box/adapter/service" - C "github.com/sagernet/sing-box/constant" + boxConstant "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/memory" + "github.com/sagernet/sing/service" ) func RegisterService(registry *boxService.Registry) { - boxService.Register[option.OOMKillerServiceOptions](registry, C.TypeOOMKiller, NewService) + boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService) } type Service struct { boxService.Adapter + logger log.ContextLogger + router adapter.Router + adaptiveTimer *adaptiveTimer + timerConfig timerConfig + hasTimerMode bool + useAvailable bool + memoryLimit uint64 } func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) { - return &Service{ - Adapter: boxService.NewAdapter(C.TypeOOMKiller, tag), - }, nil + s := &Service{ + Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag), + logger: logger, + router: service.FromContext[adapter.Router](ctx), + } + + if options.MemoryLimit != nil { + s.memoryLimit = options.MemoryLimit.Value() + } + if s.memoryLimit > 0 { + s.hasTimerMode = true + } else if memory.AvailableSupported() { + s.useAvailable = true + s.hasTimerMode = true + } + + config, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable) + if err != nil { + return nil, err + } + s.timerConfig = config + + return s, nil } func (s *Service) Start(stage adapter.StartStage) error { if stage != adapter.StartStateStart { return nil } - return E.New("memory pressure monitoring is not available on this platform") + if !s.hasTimerMode { + return E.New("memory pressure monitoring is not available on this platform without memory_limit") + } + s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig) + s.adaptiveTimer.start(0) + if s.useAvailable { + s.logger.Info("started memory monitor with available memory detection") + } else { + s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB") + } + return nil } func (s *Service) Close() error { + if s.adaptiveTimer != nil { + s.adaptiveTimer.stop() + } return nil } diff --git a/service/oomkiller/service_timer.go b/service/oomkiller/service_timer.go new file mode 100644 index 00000000..315e1715 --- /dev/null +++ b/service/oomkiller/service_timer.go @@ -0,0 +1,158 @@ +package oomkiller + +import ( + runtimeDebug "runtime/debug" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/memory" +) + +const ( + defaultChecksBeforeLimit = 4 + defaultMinInterval = 500 * time.Millisecond + defaultMaxInterval = 10 * time.Second + defaultSafetyMargin = 5 * 1024 * 1024 +) + +type adaptiveTimer struct { + logger log.ContextLogger + router adapter.Router + memoryLimit uint64 + safetyMargin uint64 + minInterval time.Duration + maxInterval time.Duration + checksBeforeLimit int + useAvailable bool + + access sync.Mutex + timer *time.Timer + previousUsage uint64 + lastInterval time.Duration +} + +type timerConfig struct { + memoryLimit uint64 + safetyMargin uint64 + minInterval time.Duration + maxInterval time.Duration + checksBeforeLimit int + useAvailable bool +} + +func newAdaptiveTimer(logger log.ContextLogger, router adapter.Router, config timerConfig) *adaptiveTimer { + return &adaptiveTimer{ + logger: logger, + router: router, + memoryLimit: config.memoryLimit, + safetyMargin: config.safetyMargin, + minInterval: config.minInterval, + maxInterval: config.maxInterval, + checksBeforeLimit: config.checksBeforeLimit, + useAvailable: config.useAvailable, + } +} + +func (t *adaptiveTimer) start(_ uint64) { + t.access.Lock() + defer t.access.Unlock() + t.startLocked() +} + +func (t *adaptiveTimer) startNow() { + t.access.Lock() + t.startLocked() + t.access.Unlock() + t.poll() +} + +func (t *adaptiveTimer) startLocked() { + if t.timer != nil { + return + } + t.previousUsage = memory.Total() + t.lastInterval = t.minInterval + t.timer = time.AfterFunc(t.minInterval, t.poll) +} + +func (t *adaptiveTimer) stop() { + t.access.Lock() + defer t.access.Unlock() + t.stopLocked() +} + +func (t *adaptiveTimer) stopLocked() { + if t.timer != nil { + t.timer.Stop() + t.timer = nil + } +} + +func (t *adaptiveTimer) running() bool { + t.access.Lock() + defer t.access.Unlock() + return t.timer != nil +} + +func (t *adaptiveTimer) poll() { + t.access.Lock() + defer t.access.Unlock() + if t.timer == nil { + return + } + + usage := memory.Total() + delta := int64(usage) - int64(t.previousUsage) + t.previousUsage = usage + + var remaining uint64 + var triggered bool + + if t.memoryLimit > 0 { + if usage >= t.memoryLimit { + remaining = 0 + triggered = true + } else { + remaining = t.memoryLimit - usage + } + } else if t.useAvailable { + available := memory.Available() + if available <= t.safetyMargin { + remaining = 0 + triggered = true + } else { + remaining = available - t.safetyMargin + } + } else { + remaining = 0 + } + + if triggered { + t.logger.Error("memory threshold reached, usage: ", usage/(1024*1024), " MiB, resetting network") + t.router.ResetNetwork() + runtimeDebug.FreeOSMemory() + } + + var interval time.Duration + if triggered { + interval = t.maxInterval + } else if delta <= 0 { + interval = t.maxInterval + } else if t.checksBeforeLimit <= 0 { + interval = t.maxInterval + } else { + timeToLimit := time.Duration(float64(remaining) / float64(delta) * float64(t.lastInterval)) + interval = timeToLimit / time.Duration(t.checksBeforeLimit) + if interval < t.minInterval { + interval = t.minInterval + } + if interval > t.maxInterval { + interval = t.maxInterval + } + } + + t.lastInterval = interval + t.timer.Reset(interval) +} From 9c2cdc72030e64aafd8a4d8732e61118df9fa4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 26 Feb 2026 21:48:39 +0800 Subject: [PATCH 157/185] Fix per-outbound bind_interface --- common/dialer/default.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index ad37834c..aee14e99 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -90,7 +90,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial if networkManager != nil { defaultOptions := networkManager.DefaultOptions() - if defaultOptions.BindInterface != "" { + if defaultOptions.BindInterface != "" && !disableDefaultBind { bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) From 9d6dee7451e049af7905c7caf8bb9dc7ebc824f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 26 Feb 2026 22:08:57 +0800 Subject: [PATCH 158/185] release: Fix pacman package --- .fpm_pacman | 23 +++++++++++++++++++++++ .github/workflows/build.yml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .fpm_pacman diff --git a/.fpm_pacman b/.fpm_pacman new file mode 100644 index 00000000..8c86dfd9 --- /dev/null +++ b/.fpm_pacman @@ -0,0 +1,23 @@ +-s dir +--name sing-box +--category net +--license GPL-3.0-or-later +--description "The universal proxy platform." +--url "https://sing-box.sagernet.org/" +--maintainer "nekohasekai " +--config-files etc/sing-box/config.json +--after-install release/config/sing-box.postinst + +release/config/config.json=/etc/sing-box/config.json + +release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service +release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service +release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf +release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules +release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf + +release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash +release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish +release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box + +LICENSE=/usr/share/licenses/sing-box/LICENSE diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bbd61b85..788b20af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -373,7 +373,7 @@ jobs: sudo gem install fpm sudo apt-get update sudo apt-get install -y libarchive-tools - cp .fpm_systemd .fpm + cp .fpm_pacman .fpm fpm -t pacman \ -v "$PKG_VERSION" \ -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \ From 9bd9e9a58b65505edbf4aa1a929a4432e7b12d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 27 Feb 2026 14:57:49 +0800 Subject: [PATCH 159/185] dialer: use KeepAliveConfig for TCP keepalive --- common/dialer/default.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/dialer/default.go b/common/dialer/default.go index aee14e99..6b2379f4 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -158,8 +158,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial if keepInterval == 0 { keepInterval = C.TCPKeepAliveInterval } - dialer.KeepAlive = keepIdle - dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval)) + dialer.KeepAliveConfig = net.KeepAliveConfig{ + Enable: true, + Idle: keepIdle, + Interval: keepInterval, + } } var udpFragment bool if options.UDPFragment != nil { From fa3ab87b1133d4a3a8f0445a76fa9d1ff166bd92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 27 Feb 2026 15:00:06 +0800 Subject: [PATCH 160/185] platform: Fix gorelease build --- Makefile | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 88eb89c6..c30cd78f 100644 --- a/Makefile +++ b/Makefile @@ -249,8 +249,8 @@ lib_apple_new: $(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple lib_install: - go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11 - go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.11 + go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12 + go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12 docs: venv/bin/mkdocs serve diff --git a/go.mod b/go.mod index 37724e7e..54fb0c72 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/sagernet/cronet-go v0.0.0-20260226034600-34ec1a064c64 github.com/sagernet/cronet-go/all v0.0.0-20260226034600-34ec1a064c64 github.com/sagernet/fswatch v0.1.1 - github.com/sagernet/gomobile v0.1.11 + github.com/sagernet/gomobile v0.1.12 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07 diff --git a/go.sum b/go.sum index 8c683a95..5223e4e6 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,8 @@ github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe77 github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= -github.com/sagernet/gomobile v0.1.11 h1:niMQAspvuThup5eRZQpsGcbM76zAvnsGr7RUIpnQMDQ= -github.com/sagernet/gomobile v0.1.11/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= +github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg= +github.com/sagernet/gomobile v0.1.12/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY= github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o= github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= From 11dc5bcbe141be2bc339c5d85e7916a41a7b8f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 27 Feb 2026 15:34:16 +0800 Subject: [PATCH 161/185] Fixes in cronet-go --- .github/CRONET_GO_VERSION | 2 +- go.mod | 62 +++++++++---------- go.sum | 124 +++++++++++++++++++------------------- option/naive.go | 17 +++--- 4 files changed, 104 insertions(+), 101 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index 52565262..e438ee5d 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -34ec1a064c64f274c4e70bf7a9c7de4bb12331f6 +17c7ef18afa63b205e835c6270277b29382eb8e3 diff --git a/go.mod b/go.mod index 54fb0c72..4dd7fef8 100644 --- a/go.mod +++ b/go.mod @@ -27,8 +27,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20260226034600-34ec1a064c64 - github.com/sagernet/cronet-go/all v0.0.0-20260226034600-34ec1a064c64 + github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6 + github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.12 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -105,35 +105,35 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260226033953-5745aabe7717 // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe7717 // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index 5223e4e6..2bae7570 100644 --- a/go.sum +++ b/go.sum @@ -152,68 +152,68 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20260226034600-34ec1a064c64 h1:2hcUvxlEbzs4hbt9l0oeBpUfet2PHzaV6X6zNTDMkvQ= -github.com/sagernet/cronet-go v0.0.0-20260226034600-34ec1a064c64/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20260226034600-34ec1a064c64 h1:f5TU9VAbhtm/SMLKctjMiSNUoQso4S6kEPUm5yNglNo= -github.com/sagernet/cronet-go/all v0.0.0-20260226034600-34ec1a064c64/go.mod h1:ohbVnfJvBzdv7R87Nz0fAmkLSTF9lCKrQC2rIqsQPY4= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260226033953-5745aabe7717 h1:vtE5zVWFQuNls2jHb5edFCFYJblGY9qrqTVpoW2V/h4= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260226033953-5745aabe7717/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260226033953-5745aabe7717 h1:+d5efMdtHbizXb4Vuai/11uNpWXmwOCfVgQcICA3kgk= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260226033953-5745aabe7717 h1:6BYObS6w5sWnNLChKT1fOYJnJt/+p6du8fSyDb3DLwo= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260226033953-5745aabe7717/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260226033953-5745aabe7717 h1:UbgjfMai+duPsftAT94zr4KcnACuJhjaxQkpup6TWLk= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260226033953-5745aabe7717 h1:KRrzFzsWWgy8XPHfp2hEX7wTuc7ILQZiFKIATvuyCHU= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260226033953-5745aabe7717 h1:bTp8j/GqUsS0P2aaTcbs7alSvUoywx+YIWyoCcVA05I= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260226033953-5745aabe7717 h1:PKuCYfGpSfnzgRJjRR5em/yU5SoI4Yvx3r0C2k1Ksh4= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260226033953-5745aabe7717 h1:w5ytvTm42ncUIFbLueN/ilp2ZaHrr5GC76Kc/Oxw4qI= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260226033953-5745aabe7717 h1:2BjQH02cCF6spz/WG8bJHBPhZtF4Or5D73L+n5fZbNE= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260226033953-5745aabe7717 h1:N9KUwDODj5eP5uqdSmbMufml2nGPc7oX4H5NPAjADIs= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260226033953-5745aabe7717/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260226033953-5745aabe7717 h1:v2p6tqpQVI6RR1HamuJmkz01Ght4miQ79YhzQtOYowk= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260226033953-5745aabe7717 h1:glPUIWUUAlOII+L0UI7/JAJ8o0wqAT7qVmWB4AcPyWs= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260226033953-5745aabe7717 h1:0QKgSc5nBrR6H1ImsOlaBbVQ7onoFhwxTBwiOnUn0es= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260226033953-5745aabe7717 h1:DsrfMoWf05lLrwdo0s/6wAC/4smeDgT4+t/iJtwGD+g= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260226033953-5745aabe7717/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260226033953-5745aabe7717 h1:bYqI37NWgvxVBeWL50/ts23uGiyHv7QfH/Ylu5lvgLs= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260226033953-5745aabe7717 h1:zg8keyQA2sniD4gqMwbUVdf57M1cmGobumOtZy1CYJs= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260226033953-5745aabe7717 h1:cgyVkTyccMGiCayGgcvfc+q0b5GdhqSk+3UMmGhlCi0= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260226033953-5745aabe7717 h1:anuE3B73oIvDOGJGvenfkQTm3NY2vL84aM062t8DZ4Y= -github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= -github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260226033953-5745aabe7717 h1:kYN/amWzLOZMm5evPwlmumnswBNPeFZiKINKP73AsuA= -github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= -github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260226033953-5745aabe7717 h1:S7rlC1Knzs3+It2u6AnhGUgiLU1QUzvoV3JXWvwE4n8= -github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260226033953-5745aabe7717/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= -github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260226033953-5745aabe7717 h1:FeyB5ZyFHAWkU1DETmUjLEM9GUFWoIndV8usT4AjGOk= -github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260226033953-5745aabe7717/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= -github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260226033953-5745aabe7717 h1:loU7PsyqI7dplbFGjQKoMT4IVYuzVyfW88FIq+7gbmY= -github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= -github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260226033953-5745aabe7717 h1:XSVrZ18XauiYMf+G0CzZETteQF0r1NOoA1gVXlZWjUI= -github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= -github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260226033953-5745aabe7717 h1:oTRK4T5pQ1ZYQh7Avr5u3bsDnXkloSVqwBljRy8FHOY= -github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260226033953-5745aabe7717/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260226033953-5745aabe7717 h1:J18xRmUa9lle+Y6pYkQ7OMq2B9eox5Bmm4MkbuQPQm0= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260226033953-5745aabe7717 h1:PC9gKeDN3wmcFxwiBEM57W0YwEz1uFGqawwssEQe494= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260226033953-5745aabe7717 h1:STlbMnsydaymby26m3wNfeYYI7HoiZ6+wxQ75Si1g8E= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260226033953-5745aabe7717/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260226033953-5745aabe7717 h1:qWtpoBxmHyb49IRtMAFkJIRlL4KCYW5Fe6YptYhItpM= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe7717 h1:7ohuWJ0PswrU7Xw/xfL6OrIh7sXybGNOgh6DaL+urdY= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260226033953-5745aabe7717/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6 h1:Ato+guxmEL4uezcYV1UUUDpAv9HlcJQ7BZt2zpnzjuw= +github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6 h1:0ldSjcR5Gt/o+otTvUAmJ28FCLab9lnlpEhxRCMQpRA= +github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6/go.mod h1:xVwYoNCyv9tF7W1RJlUdDbT4bn5tyqtyTe1P1ZY2VP8= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d h1:tudlBYdQHIWctKIdf7pceBOFIUIISK6yFivwsxhxDk0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d h1:F5EsQlIknj0HlExBFR4EXW69dYj0MpK1HCpKhL/weEs= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d h1:9SQ6I2Y2radd6RyWEfV+9s1Q9Kth54B6gBHuJWNzQas= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d h1:+XoeknBi6+s6epDAS3BkEsp5zGqEJsT9L8JEcaq+0nE= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d h1:poqfhHJAg+7BtABn4cue7V4y8Kb2eZ1Cy0j+bhDangw= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d h1:nH6rtfqWbui9zQPjd18cpvZncGvn21UcVLtmeUoQKXs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d h1:HtnjWZzSQBaP29XJ5NoIps1TVZ7DUC7R0NH7IyhJ5Ag= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d h1:E2DWx0Agrj8Fi745S+otYW+W0rL2I8+Z2rZCFqGYPvQ= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d h1:j7f/rBwPlO1RpFQeM35QVHymVXGVo6d8WTz4i4SjcPo= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d h1:hz8kkcHGMe7QBTpbqkaw89ZFsfX+UN5F5RIDrroDkx8= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d h1:TNFaO19ySEyqG79j5+dYb+w4ivusrTXanWuogmC4VM0= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d h1:Ewc/wR3yu/hOwG/p49nI9TwYmYv3Llm5DA6fSb1w8hY= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d h1:PJ24NkPNpMrLGNRdb6moEqJo8gfhYcIRZmQD8jPPCJk= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d h1:IaUghNA8cOmmwvzUPKPsfhiG0KmpWpE0mFZl85T5/Bw= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d h1:whbeDcr9dDWPr45Is9QV6OHAncrBWLJtPuo4uyEJFBg= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d h1:ecHgaGMvikNYjsfULekdXjL/cQJXCS38yvHaKVMWtXc= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d h1:no7Cb54+vv1bQ39zFp+JIHKO8Tu3sTwqz8SoOAuV/Ek= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d h1:DqBSbam9KAzBgDInOoNy4K0baSJyxGWESxrDewU5aSs= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d h1:fOR5i+hRyjG8ZzPSG6URkoTKr5qYOJfxZ58zd8HBteM= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d h1:hEQGQI+PfUzYBVas4NWw8WiEUsATco6vwv+t4qTtgtw= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d h1:AzzJ5AtZlwTbU5QOSixZdPLTjzWKCun3AobQChKy0W8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d h1:9Tp7s/WX4DZLx4ues8G38G2OV7eQbeuU2COEZEbGcF0= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d h1:T9EVZKTyZHOamwevomUZnJ6TQNc09I/BwK+L5HJCJj8= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d h1:FZmThI7xScJRPERFiA4L2l9KCwA0oi8/lEOajIKEtUQ= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d h1:BCC/b8bL0dD9Q4ghgKABV/EsMe0J8GE/l7hcRdPkUXQ= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d h1:3l463BXnC/X42ow2zqHm9Y/K4GM6aRsKUIZBcFxr2+Q= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d h1:+XHEZ/z5NgPfjOAzOwfbQzR+42qaDNB0nv+fAOcd6Pc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d h1:sYWbP+qCt9Rhb1yGaIRY7HVLtaQZmrHWR0obc5+Q1qc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d h1:r6eOVlAfmcUMD5nfz+mPd/aORevUKhcvxA1z1GdPnG8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg= diff --git a/option/naive.go b/option/naive.go index fcc315b6..da3a88db 100644 --- a/option/naive.go +++ b/option/naive.go @@ -2,6 +2,7 @@ package option import ( "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/byteformats" "github.com/sagernet/sing/common/json/badoption" ) @@ -26,12 +27,14 @@ type NaiveInboundOptions struct { type NaiveOutboundOptions struct { DialerOptions ServerOptions - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - InsecureConcurrency int `json:"insecure_concurrency,omitempty"` - ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"` - UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` - QUIC bool `json:"quic,omitempty"` - QUICCongestionControl string `json:"quic_congestion_control,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + InsecureConcurrency int `json:"insecure_concurrency,omitempty"` + ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"` + ReceiveWindow *byteformats.MemoryBytes `json:"stream_receive_window,omitempty"` + UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"` + QUIC bool `json:"quic,omitempty"` + QUICCongestionControl string `json:"quic_congestion_control,omitempty"` + QUICSessionReceiveWindow *byteformats.MemoryBytes `json:"quic_session_receive_window,omitempty"` OutboundTLSOptionsContainer } From 93b7328c3fdbabc07f981e305f6050dca94b50f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 27 Feb 2026 18:18:33 +0800 Subject: [PATCH 162/185] Fix missing Tailscale in ProxyDisplayName --- constant/proxy.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/constant/proxy.go b/constant/proxy.go index 4130c631..278a46c2 100644 --- a/constant/proxy.go +++ b/constant/proxy.go @@ -86,6 +86,8 @@ func ProxyDisplayName(proxyType string) string { return "Hysteria2" case TypeAnyTLS: return "AnyTLS" + case TypeTailscale: + return "Tailscale" case TypeSelector: return "Selector" case TypeURLTest: From 13e6ba4cb23147d68108652fd4f5e71009802428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 27 Feb 2026 19:55:27 +0800 Subject: [PATCH 163/185] Update tfo-go --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4dd7fef8..0621134c 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/caddyserver/certmagic v0.25.0 github.com/coder/websocket v1.8.14 github.com/cretz/bine v0.2.0 - github.com/database64128/tfo-go/v2 v2.3.1 + github.com/database64128/tfo-go/v2 v2.3.2 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/render v1.0.3 github.com/godbus/dbus/v5 v5.2.1 @@ -54,7 +54,7 @@ require ( golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 golang.org/x/mod v0.31.0 golang.org/x/net v0.48.0 - golang.org/x/sys v0.39.0 + golang.org/x/sys v0.41.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.77.0 google.golang.org/protobuf v1.36.11 diff --git a/go.sum b/go.sum index 2bae7570..987a87f6 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM= github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc= -github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw= -github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU= +github.com/database64128/tfo-go/v2 v2.3.2 h1:UhZMKiMq3swZGUiETkLBDzQnZBPSAeBMClpJGlnJ5Fw= +github.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -346,8 +346,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= From 6da7e538e122b77c226ce48c1a6b4ecb61fd3fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 27 Feb 2026 15:08:52 +0800 Subject: [PATCH 164/185] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/clients/android b/clients/android index 4bdde0ae..7d1e7c72 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 4bdde0ae4db2f0fe041a52520e12315141d0981c +Subproject commit 7d1e7c72cebdce23ea4f5f3dcfc8d12a4dc29633 diff --git a/clients/apple b/clients/apple index 015dcd26..80c86686 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 015dcd266ba8651f5be20de531bf9184470f750d +Subproject commit 80c866861df86b43d597e86b086458ff8d6c103b diff --git a/docs/changelog.md b/docs/changelog.md index d3162824..b67663e1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,9 +2,7 @@ icon: material/alert-decagram --- -#### 1.13.0-rc.6 - -* Fixes and improvements +#### 1.13.0 Important changes since 1.12: @@ -22,7 +20,7 @@ Important changes since 1.12: * Improve `local` DNS server **12** * Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for listen and dial fields **13** * Add `bind_address_no_port` option for dial fields **14** -* Add system interface and relay server options for Tailscale endpoint **15** +* Add system interface, relay server and advertise tags options for Tailscale endpoint **15** * Add Claude Code Multiplexer service **16** * Add OpenAI Codex Multiplexer service **17** * Apple/Android: Refactor GUI @@ -136,6 +134,7 @@ See [Dial Fields](/configuration/shared/dial/#bind_address_no_port). Tailscale endpoint can now create a system TUN interface to handle traffic directly. New `relay_server_port` and `relay_server_static_endpoints` options for incoming relay connections. +New `advertise_tags` option for ACL tag advertisement. See [Tailscale endpoint](/configuration/endpoint/tailscale/). @@ -169,6 +168,10 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu uTLS is not recommended for censorship circumvention due to fundamental architectural limitations; use NaiveProxy instead for TLS fingerprint resistance. +#### 1.12.23 + +* Fixes and improvements + #### 1.13.0-rc.5 * Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound From 8ae93a98e581d9a89f0019cbf3101301c2d411b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 28 Feb 2026 17:55:56 +0800 Subject: [PATCH 165/185] Remove overdue deprecated features --- adapter/inbound.go | 11 +- common/listener/listener_tcp.go | 2 - common/tls/ech.go | 5 +- experimental/deprecated/constants.go | 110 ---------------- include/registry.go | 6 +- include/wireguard.go | 5 - include/wireguard_stub.go | 7 - option/direct.go | 5 +- option/inbound.go | 2 +- option/outbound.go | 6 +- option/wireguard.go | 25 ---- protocol/anytls/inbound.go | 1 - protocol/direct/inbound.go | 1 - protocol/direct/outbound.go | 89 ++----------- protocol/hysteria/inbound.go | 2 - protocol/hysteria2/inbound.go | 2 - protocol/naive/inbound.go | 1 - protocol/shadowsocks/inbound_multi.go | 2 - protocol/shadowsocks/inbound_relay.go | 2 - protocol/shadowtls/inbound.go | 1 - protocol/trojan/inbound.go | 1 - protocol/tuic/inbound.go | 2 - protocol/tun/inbound.go | 106 +++++----------- protocol/vless/inbound.go | 1 - protocol/vmess/inbound.go | 1 - protocol/wireguard/outbound.go | 176 -------------------------- route/route.go | 32 ----- route/rule/rule_default.go | 10 +- route/rule/rule_dns.go | 10 +- test/domain_inbound_test.go | 3 - test/inbound_detour_test.go | 4 +- test/shadowtls_test.go | 9 +- 32 files changed, 67 insertions(+), 573 deletions(-) delete mode 100644 protocol/wireguard/outbound.go diff --git a/adapter/inbound.go b/adapter/inbound.go index 1941df5b..b32e9f82 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -62,13 +62,10 @@ type InboundContext struct { // cache // Deprecated: implement in rule action - InboundDetour string - LastInbound string - OriginDestination M.Socksaddr - RouteOriginalDestination M.Socksaddr - // Deprecated: to be removed - //nolint:staticcheck - InboundOptions option.InboundOptions + InboundDetour string + LastInbound string + OriginDestination M.Socksaddr + RouteOriginalDestination M.Socksaddr UDPDisableDomainUnmapping bool UDPConnect bool UDPTimeout time.Duration diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index 2164ff8e..899d444f 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -99,8 +99,6 @@ func (l *Listener) loopTCPIn() { } //nolint:staticcheck metadata.InboundDetour = l.listenOptions.Detour - //nolint:staticcheck - metadata.InboundOptions = l.listenOptions.InboundOptions metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() ctx := log.ContextWithNewID(l.ctx) diff --git a/common/tls/ech.go b/common/tls/ech.go index 37573bf1..8c884cab 100644 --- a/common/tls/ech.go +++ b/common/tls/ech.go @@ -15,7 +15,6 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" aTLS "github.com/sagernet/sing/common/tls" @@ -38,7 +37,7 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op } //nolint:staticcheck if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled { - deprecated.Report(ctx, deprecated.OptionLegacyECHOptions) + return nil, E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0") } if len(echConfig) > 0 { block, rest := pem.Decode(echConfig) @@ -77,7 +76,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, tlsConfig.EncryptedClientHelloKeys = echKeys //nolint:staticcheck if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled { - deprecated.Report(ctx, deprecated.OptionLegacyECHOptions) + return E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0") } return nil } diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go index 5dfdfd47..385105d3 100644 --- a/experimental/deprecated/constants.go +++ b/experimental/deprecated/constants.go @@ -57,96 +57,6 @@ func (n Note) MessageWithLink() string { } } -var OptionBadMatchSource = Note{ - Name: "bad-match-source", - Description: "legacy match source rule item", - DeprecatedVersion: "1.10.0", - ScheduledVersion: "1.11.0", - EnvName: "BAD_MATCH_SOURCE", - MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed", -} - -var OptionGEOIP = Note{ - Name: "geoip", - Description: "geoip database", - DeprecatedVersion: "1.8.0", - ScheduledVersion: "1.12.0", - EnvName: "GEOIP", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geoip-to-rule-sets", -} - -var OptionGEOSITE = Note{ - Name: "geosite", - Description: "geosite database", - DeprecatedVersion: "1.8.0", - ScheduledVersion: "1.12.0", - EnvName: "GEOSITE", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geosite-to-rule-sets", -} - -var OptionTUNAddressX = Note{ - Name: "tun-address-x", - Description: "legacy tun address fields", - DeprecatedVersion: "1.10.0", - ScheduledVersion: "1.12.0", - EnvName: "TUN_ADDRESS_X", - MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged", -} - -var OptionSpecialOutbounds = Note{ - Name: "special-outbounds", - Description: "legacy special outbounds", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.13.0", - EnvName: "SPECIAL_OUTBOUNDS", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", -} - -var OptionInboundOptions = Note{ - Name: "inbound-options", - Description: "legacy inbound fields", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.13.0", - EnvName: "INBOUND_OPTIONS", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions", -} - -var OptionDestinationOverrideFields = Note{ - Name: "destination-override-fields", - Description: "destination override fields in direct outbound", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.13.0", - EnvName: "DESTINATION_OVERRIDE_FIELDS", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-destination-override-fields-to-route-options", -} - -var OptionWireGuardOutbound = Note{ - Name: "wireguard-outbound", - Description: "legacy wireguard outbound", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.13.0", - EnvName: "WIREGUARD_OUTBOUND", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-wireguard-outbound-to-endpoint", -} - -var OptionWireGuardGSO = Note{ - Name: "wireguard-gso", - Description: "GSO option in wireguard outbound", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.13.0", - EnvName: "WIREGUARD_GSO", - MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-wireguard-outbound-to-endpoint", -} - -var OptionTUNGSO = Note{ - Name: "tun-gso", - Description: "GSO option in tun", - DeprecatedVersion: "1.11.0", - ScheduledVersion: "1.12.0", - EnvName: "TUN_GSO", - MigrationLink: "https://sing-box.sagernet.org/deprecated/#gso-option-in-tun", -} - var OptionLegacyDNSTransport = Note{ Name: "legacy-dns-transport", Description: "legacy DNS servers", @@ -183,15 +93,6 @@ var OptionMissingDomainResolver = Note{ MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-outbound-dns-rule-items-to-domain-resolver", } -var OptionLegacyECHOptions = Note{ - Name: "legacy-ech-options", - Description: "legacy ECH options", - DeprecatedVersion: "1.12.0", - ScheduledVersion: "1.13.0", - EnvName: "LEGACY_ECH_OPTIONS", - MigrationLink: "https://sing-box.sagernet.org/deprecated/#legacy-ech-fields", -} - var OptionLegacyDomainStrategyOptions = Note{ Name: "legacy-domain-strategy-options", Description: "legacy domain strategy options", @@ -202,20 +103,9 @@ var OptionLegacyDomainStrategyOptions = Note{ } var Options = []Note{ - OptionBadMatchSource, - OptionGEOIP, - OptionGEOSITE, - OptionTUNAddressX, - OptionSpecialOutbounds, - OptionInboundOptions, - OptionDestinationOverrideFields, - OptionWireGuardOutbound, - OptionWireGuardGSO, - OptionTUNGSO, OptionLegacyDNSTransport, OptionLegacyDNSFakeIPOptions, OptionOutboundDNSRuleItem, OptionMissingDomainResolver, - OptionLegacyECHOptions, OptionLegacyDomainStrategyOptions, } diff --git a/include/registry.go b/include/registry.go index 64a49b61..f090845b 100644 --- a/include/registry.go +++ b/include/registry.go @@ -20,7 +20,6 @@ import ( "github.com/sagernet/sing-box/protocol/anytls" "github.com/sagernet/sing-box/protocol/block" "github.com/sagernet/sing-box/protocol/direct" - protocolDNS "github.com/sagernet/sing-box/protocol/dns" "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing-box/protocol/http" "github.com/sagernet/sing-box/protocol/mixed" @@ -76,7 +75,6 @@ func OutboundRegistry() *outbound.Registry { direct.RegisterOutbound(registry) block.RegisterOutbound(registry) - protocolDNS.RegisterOutbound(registry) group.RegisterSelector(registry) group.RegisterURLTest(registry) @@ -94,7 +92,6 @@ func OutboundRegistry() *outbound.Registry { anytls.RegisterOutbound(registry) registerQUICOutbounds(registry) - registerWireGuardOutbound(registry) registerStubForRemovedOutbounds(registry) return registry @@ -152,4 +149,7 @@ func registerStubForRemovedOutbounds(registry *outbound.Registry) { outbound.Register[option.ShadowsocksROutboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") }) + outbound.Register[option.StubOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { + return nil, E.New("WireGuard outbound is deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use WireGuard endpoint instead") + }) } diff --git a/include/wireguard.go b/include/wireguard.go index f2ce9e23..fa7cfe6f 100644 --- a/include/wireguard.go +++ b/include/wireguard.go @@ -4,14 +4,9 @@ package include import ( "github.com/sagernet/sing-box/adapter/endpoint" - "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/protocol/wireguard" ) -func registerWireGuardOutbound(registry *outbound.Registry) { - wireguard.RegisterOutbound(registry) -} - func registerWireGuardEndpoint(registry *endpoint.Registry) { wireguard.RegisterEndpoint(registry) } diff --git a/include/wireguard_stub.go b/include/wireguard_stub.go index 247546e2..e03a9d9c 100644 --- a/include/wireguard_stub.go +++ b/include/wireguard_stub.go @@ -7,19 +7,12 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/endpoint" - "github.com/sagernet/sing-box/adapter/outbound" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" ) -func registerWireGuardOutbound(registry *outbound.Registry) { - outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.LegacyWireGuardOutboundOptions) (adapter.Outbound, error) { - return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) - }) -} - func registerWireGuardEndpoint(registry *endpoint.Registry) { endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) { return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) diff --git a/option/direct.go b/option/direct.go index 180ff0aa..a03f98d4 100644 --- a/option/direct.go +++ b/option/direct.go @@ -3,7 +3,7 @@ package option import ( "context" - "github.com/sagernet/sing-box/experimental/deprecated" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" ) @@ -31,8 +31,9 @@ func (d *DirectOutboundOptions) UnmarshalJSONContext(ctx context.Context, conten if err != nil { return err } + //nolint:staticcheck if d.OverrideAddress != "" || d.OverridePort != 0 { - deprecated.Report(ctx, deprecated.OptionDestinationOverrideFields) + return E.New("destination override fields in direct outbound are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use route options instead") } return nil } diff --git a/option/inbound.go b/option/inbound.go index 42930ecc..4fb6081d 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -55,7 +55,6 @@ type InboundOptions struct { SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` - Detour string `json:"detour,omitempty"` } type ListenOptions struct { @@ -73,6 +72,7 @@ type ListenOptions struct { UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + Detour string `json:"detour,omitempty"` // Deprecated: removed ProxyProtocol bool `json:"proxy_protocol,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index 2ed7cf2e..cb388c44 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -4,7 +4,6 @@ import ( "context" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" @@ -40,7 +39,7 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err } switch h.Type { case C.TypeDNS: - deprecated.Report(ctx, deprecated.OptionSpecialOutbounds) + return E.New("dns outbound is deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use rule actions instead") } options, loaded := registry.CreateOptions(h.Type) if !loaded { @@ -51,8 +50,9 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err return err } if listenWrapper, isListen := options.(ListenOptionsWrapper); isListen { + //nolint:staticcheck if listenWrapper.TakeListenOptions().InboundOptions != (InboundOptions{}) { - deprecated.Report(ctx, deprecated.OptionInboundOptions) + return E.New("legacy inbound fields are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, use rule actions instead") } } h.Options = options diff --git a/option/wireguard.go b/option/wireguard.go index 43d3139c..c86abd11 100644 --- a/option/wireguard.go +++ b/option/wireguard.go @@ -28,28 +28,3 @@ type WireGuardPeer struct { PersistentKeepaliveInterval uint16 `json:"persistent_keepalive_interval,omitempty"` Reserved []uint8 `json:"reserved,omitempty"` } - -type LegacyWireGuardOutboundOptions struct { - DialerOptions - SystemInterface bool `json:"system_interface,omitempty"` - GSO bool `json:"gso,omitempty"` - InterfaceName string `json:"interface_name,omitempty"` - LocalAddress badoption.Listable[netip.Prefix] `json:"local_address"` - PrivateKey string `json:"private_key"` - Peers []LegacyWireGuardPeer `json:"peers,omitempty"` - ServerOptions - PeerPublicKey string `json:"peer_public_key"` - PreSharedKey string `json:"pre_shared_key,omitempty"` - Reserved []uint8 `json:"reserved,omitempty"` - Workers int `json:"workers,omitempty"` - MTU uint32 `json:"mtu,omitempty"` - Network NetworkList `json:"network,omitempty"` -} - -type LegacyWireGuardPeer struct { - ServerOptions - PublicKey string `json:"public_key,omitempty"` - PreSharedKey string `json:"pre_shared_key,omitempty"` - AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"` - Reserved []uint8 `json:"reserved,omitempty"` -} diff --git a/protocol/anytls/inbound.go b/protocol/anytls/inbound.go index 662c7788..52d77353 100644 --- a/protocol/anytls/inbound.go +++ b/protocol/anytls/inbound.go @@ -122,7 +122,6 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination.Unwrap() if userName, _ := auth.UserFromContext[string](ctx); userName != "" { diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index a96e8326..81353b65 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -111,7 +111,6 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, //nolint:staticcheck metadata.InboundDetour = i.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = i.listener.ListenOptions().InboundOptions metadata.Source = source destination = i.listener.UDPAddr() switch i.overrideOption { diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 1f24f0c9..9d24f31a 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -16,7 +16,6 @@ import ( "github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common" - "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" @@ -36,14 +35,12 @@ var ( type Outbound struct { outbound.Adapter - ctx context.Context - logger logger.ContextLogger - dialer dialer.ParallelInterfaceDialer - domainStrategy C.DomainStrategy - fallbackDelay time.Duration - overrideOption int - overrideDestination M.Socksaddr - isEmpty bool + ctx context.Context + logger logger.ContextLogger + dialer dialer.ParallelInterfaceDialer + domainStrategy C.DomainStrategy + fallbackDelay time.Duration + isEmpty bool // loopBack *loopBackDetector } @@ -69,25 +66,13 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL domainStrategy: C.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer.(dialer.ParallelInterfaceDialer), - //nolint:staticcheck - isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}) && options.OverrideAddress == "" && options.OverridePort == 0, + isEmpty: reflect.DeepEqual(options.DialerOptions, option.DialerOptions{UDPFragmentDefault: true}), // loopBack: newLoopBackDetector(router), } //nolint:staticcheck if options.ProxyProtocol != 0 { return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") } - //nolint:staticcheck - if options.OverrideAddress != "" && options.OverridePort != 0 { - outbound.overrideOption = 1 - outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverrideAddress != "" { - outbound.overrideOption = 2 - outbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverridePort != 0 { - outbound.overrideOption = 3 - outbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} - } return outbound, nil } @@ -95,16 +80,6 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination - switch h.overrideOption { - case 1: - destination = h.overrideDestination - case 2: - newDestination := h.overrideDestination - newDestination.Port = destination.Port - destination = newDestination - case 3: - destination.Port = h.overrideDestination.Port - } network = N.NetworkName(network) switch network { case N.NetworkTCP: @@ -124,30 +99,12 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination - originDestination := destination - switch h.overrideOption { - case 1: - destination = h.overrideDestination - case 2: - newDestination := h.overrideDestination - newDestination.Port = destination.Port - destination = newDestination - case 3: - destination.Port = h.overrideDestination.Port - } - if h.overrideOption == 0 { - h.logger.InfoContext(ctx, "outbound packet connection") - } else { - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } + h.logger.InfoContext(ctx, "outbound packet connection") conn, err := h.dialer.ListenPacket(ctx, destination) if err != nil { return nil, err } // conn = h.loopBack.NewPacketConn(bufio.NewPacketConn(conn), destination) - if originDestination != destination { - conn = bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, originDestination) - } return conn, nil } @@ -165,13 +122,6 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination - switch h.overrideOption { - case 1, 2: - // override address - return h.DialContext(ctx, network, destination) - case 3: - destination.Port = h.overrideDestination.Port - } network = N.NetworkName(network) switch network { case N.NetworkTCP: @@ -186,13 +136,6 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination - switch h.overrideOption { - case 1, 2: - // override address - return h.DialContext(ctx, network, destination) - case 3: - destination.Port = h.overrideDestination.Port - } network = N.NetworkName(network) switch network { case N.NetworkTCP: @@ -207,21 +150,7 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M. ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination - switch h.overrideOption { - case 1: - destination = h.overrideDestination - case 2: - newDestination := h.overrideDestination - newDestination.Port = destination.Port - destination = newDestination - case 3: - destination.Port = h.overrideDestination.Port - } - if h.overrideOption == 0 { - h.logger.InfoContext(ctx, "outbound packet connection") - } else { - h.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } + h.logger.InfoContext(ctx, "outbound packet connection") conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) if err != nil { return nil, netip.Addr{}, err diff --git a/protocol/hysteria/inbound.go b/protocol/hysteria/inbound.go index 5afc440d..98d7cb81 100644 --- a/protocol/hysteria/inbound.go +++ b/protocol/hysteria/inbound.go @@ -118,7 +118,6 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source metadata.Destination = destination @@ -141,7 +140,6 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source metadata.Destination = destination diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index f55b6ae8..bb598070 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -151,7 +151,6 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source metadata.Destination = destination @@ -174,7 +173,6 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source metadata.Destination = destination diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index 83938594..48c35926 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -209,7 +209,6 @@ func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net //nolint:staticcheck metadata.InboundDetour = n.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = n.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() diff --git a/protocol/shadowsocks/inbound_multi.go b/protocol/shadowsocks/inbound_multi.go index 0120a08a..7ff92646 100644 --- a/protocol/shadowsocks/inbound_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -175,7 +175,6 @@ func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadat //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions if h.tracker != nil { conn = h.tracker.TrackConnection(conn, metadata) } @@ -201,7 +200,6 @@ func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketCon //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions if h.tracker != nil { conn = h.tracker.TrackPacketConnection(conn, metadata) } diff --git a/protocol/shadowsocks/inbound_relay.go b/protocol/shadowsocks/inbound_relay.go index 9760b2f0..d7d7bcff 100644 --- a/protocol/shadowsocks/inbound_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -135,7 +135,6 @@ func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadat //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RouteConnection(ctx, conn, metadata) } @@ -158,7 +157,6 @@ func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketCon //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions return h.router.RoutePacketConnection(ctx, conn, metadata) } diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 812df1ef..17afa268 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -129,7 +129,6 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.Source = source metadata.Destination = destination if userName, _ := auth.UserFromContext[string](ctx); userName != "" { diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index 24e8a023..6e11c088 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -257,7 +257,6 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/protocol/tuic/inbound.go b/protocol/tuic/inbound.go index c4c63236..600c7f93 100644 --- a/protocol/tuic/inbound.go +++ b/protocol/tuic/inbound.go @@ -108,7 +108,6 @@ func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source metadata.Destination = destination @@ -131,7 +130,6 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.OriginDestination = h.listener.UDPAddr() metadata.Source = source metadata.Destination = destination diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 70f82044..df9344b8 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -14,7 +14,6 @@ import ( "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/route/rule" @@ -36,13 +35,11 @@ func RegisterInbound(registry *inbound.Registry) { } type Inbound struct { - tag string - ctx context.Context - router adapter.Router - networkManager adapter.NetworkManager - logger log.ContextLogger - //nolint:staticcheck - inboundOptions option.InboundOptions + tag string + ctx context.Context + router adapter.Router + networkManager adapter.NetworkManager + logger log.ContextLogger tunOptions tun.Options udpTimeout time.Duration stack string @@ -60,20 +57,18 @@ type Inbound struct { } func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { + //nolint:staticcheck + if len(options.Inet4Address) > 0 || len(options.Inet6Address) > 0 || + len(options.Inet4RouteAddress) > 0 || len(options.Inet6RouteAddress) > 0 || + len(options.Inet4RouteExcludeAddress) > 0 || len(options.Inet6RouteExcludeAddress) > 0 { + return nil, E.New("legacy tun address fields are deprecated in sing-box 1.10.0 and removed in sing-box 1.12.0") + } + //nolint:staticcheck + if options.GSO { + return nil, E.New("GSO option in tun is deprecated in sing-box 1.11.0 and removed in sing-box 1.12.0") + } + address := options.Address - var deprecatedAddressUsed bool - - //nolint:staticcheck - if len(options.Inet4Address) > 0 { - address = append(address, options.Inet4Address...) - deprecatedAddressUsed = true - } - - //nolint:staticcheck - if len(options.Inet6Address) > 0 { - address = append(address, options.Inet6Address...) - deprecatedAddressUsed = true - } inet4Address := common.Filter(address, func(it netip.Prefix) bool { return it.Addr().Is4() }) @@ -82,18 +77,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo }) routeAddress := options.RouteAddress - - //nolint:staticcheck - if len(options.Inet4RouteAddress) > 0 { - routeAddress = append(routeAddress, options.Inet4RouteAddress...) - deprecatedAddressUsed = true - } - - //nolint:staticcheck - if len(options.Inet6RouteAddress) > 0 { - routeAddress = append(routeAddress, options.Inet6RouteAddress...) - deprecatedAddressUsed = true - } inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool { return it.Addr().Is4() }) @@ -102,18 +85,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo }) routeExcludeAddress := options.RouteExcludeAddress - - //nolint:staticcheck - if len(options.Inet4RouteExcludeAddress) > 0 { - routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...) - deprecatedAddressUsed = true - } - - //nolint:staticcheck - if len(options.Inet6RouteExcludeAddress) > 0 { - routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...) - deprecatedAddressUsed = true - } inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool { return it.Addr().Is4() }) @@ -121,15 +92,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return it.Addr().Is6() }) - if deprecatedAddressUsed { - deprecated.Report(ctx, deprecated.OptionTUNAddressX) - } - - //nolint:staticcheck - if options.GSO { - deprecated.Report(ctx, deprecated.OptionTUNGSO) - } - platformInterface := service.FromContext[adapter.PlatformInterface](ctx) tunMTU := options.MTU enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152 @@ -202,7 +164,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo router: router, networkManager: networkManager, logger: logger, - inboundOptions: options.InboundOptions, tunOptions: tun.Options{ Name: options.InterfaceName, MTU: tunMTU, @@ -478,13 +439,12 @@ func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destinat ipVersion = 6 } routeDestination, err := t.router.PreMatch(adapter.InboundContext{ - Inbound: t.tag, - InboundType: C.TypeTun, - IPVersion: ipVersion, - Network: network, - Source: source, - Destination: destination, - InboundOptions: t.inboundOptions, + Inbound: t.tag, + InboundType: C.TypeTun, + IPVersion: ipVersion, + Network: network, + Source: source, + Destination: destination, }, routeContext, timeout, false) if err != nil { switch { @@ -508,8 +468,7 @@ func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.S metadata.InboundType = C.TypeTun metadata.Source = source metadata.Destination = destination - //nolint:staticcheck - metadata.InboundOptions = t.inboundOptions + t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) t.router.RouteConnectionEx(ctx, conn, metadata, onClose) @@ -522,8 +481,7 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata.InboundType = C.TypeTun metadata.Source = source metadata.Destination = destination - //nolint:staticcheck - metadata.InboundOptions = t.inboundOptions + t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) @@ -539,13 +497,12 @@ func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksad ipVersion = 6 } routeDestination, err := t.router.PreMatch(adapter.InboundContext{ - Inbound: t.tag, - InboundType: C.TypeTun, - IPVersion: ipVersion, - Network: network, - Source: source, - Destination: destination, - InboundOptions: t.inboundOptions, + Inbound: t.tag, + InboundType: C.TypeTun, + IPVersion: ipVersion, + Network: network, + Source: source, + Destination: destination, }, routeContext, timeout, true) if err != nil { switch { @@ -569,8 +526,7 @@ func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn metadata.InboundType = C.TypeTun metadata.Source = source metadata.Destination = destination - //nolint:staticcheck - metadata.InboundOptions = t.inboundOptions + t.logger.InfoContext(ctx, "inbound redirect connection from ", metadata.Source) t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) t.router.RouteConnectionEx(ctx, conn, metadata, onClose) diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index 1df7cb01..75cd4124 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -217,7 +217,6 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/protocol/vmess/inbound.go b/protocol/vmess/inbound.go index 059d4775..4e9c763c 100644 --- a/protocol/vmess/inbound.go +++ b/protocol/vmess/inbound.go @@ -223,7 +223,6 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net. //nolint:staticcheck metadata.InboundDetour = h.listener.ListenOptions().Detour //nolint:staticcheck - metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go deleted file mode 100644 index 5b08c6a7..00000000 --- a/protocol/wireguard/outbound.go +++ /dev/null @@ -1,176 +0,0 @@ -package wireguard - -import ( - "context" - "net" - "net/netip" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/adapter/outbound" - "github.com/sagernet/sing-box/common/dialer" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/transport/wireguard" - tun "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" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/service" -) - -var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil) - -func RegisterOutbound(registry *outbound.Registry) { - outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) -} - -type Outbound struct { - outbound.Adapter - ctx context.Context - dnsRouter adapter.DNSRouter - logger logger.ContextLogger - localAddresses []netip.Prefix - endpoint *wireguard.Endpoint -} - -func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.LegacyWireGuardOutboundOptions) (adapter.Outbound, error) { - deprecated.Report(ctx, deprecated.OptionWireGuardOutbound) - if options.GSO { - deprecated.Report(ctx, deprecated.OptionWireGuardGSO) - } - outbound := &Outbound{ - Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions), - ctx: ctx, - dnsRouter: service.FromContext[adapter.DNSRouter](ctx), - logger: logger, - localAddresses: options.LocalAddress, - } - if options.Detour != "" && options.GSO { - return nil, E.New("gso is conflict with detour") - } - outboundDialer, err := dialer.NewWithOptions(dialer.Options{ - Context: ctx, - Options: options.DialerOptions, - RemoteIsDomain: options.ServerIsDomain() || common.Any(options.Peers, func(it option.LegacyWireGuardPeer) bool { - return it.ServerIsDomain() - }), - ResolverOnDetour: true, - }) - if err != nil { - return nil, err - } - peers := common.Map(options.Peers, func(it option.LegacyWireGuardPeer) wireguard.PeerOptions { - return wireguard.PeerOptions{ - Endpoint: it.ServerOptions.Build(), - PublicKey: it.PublicKey, - PreSharedKey: it.PreSharedKey, - AllowedIPs: it.AllowedIPs, - // PersistentKeepaliveInterval: time.Duration(it.PersistentKeepaliveInterval), - Reserved: it.Reserved, - } - }) - if len(peers) == 0 { - peers = []wireguard.PeerOptions{{ - Endpoint: options.ServerOptions.Build(), - PublicKey: options.PeerPublicKey, - PreSharedKey: options.PreSharedKey, - AllowedIPs: []netip.Prefix{netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.PrefixFrom(netip.IPv6Unspecified(), 0)}, - Reserved: options.Reserved, - }} - } - wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ - Context: ctx, - Logger: logger, - System: options.SystemInterface, - Dialer: outboundDialer, - CreateDialer: func(interfaceName string) N.Dialer { - return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{ - BindInterface: interfaceName, - })) - }, - Name: options.InterfaceName, - MTU: options.MTU, - Address: options.LocalAddress, - PrivateKey: options.PrivateKey, - ResolvePeer: func(domain string) (netip.Addr, error) { - endpointAddresses, lookupErr := outbound.dnsRouter.Lookup(ctx, domain, outboundDialer.(dialer.ResolveDialer).QueryOptions()) - if lookupErr != nil { - return netip.Addr{}, lookupErr - } - return endpointAddresses[0], nil - }, - Peers: peers, - Workers: options.Workers, - }) - if err != nil { - return nil, err - } - outbound.endpoint = wgEndpoint - return outbound, nil -} - -func (o *Outbound) Start(stage adapter.StartStage) error { - switch stage { - case adapter.StartStateStart: - return o.endpoint.Start(false) - case adapter.StartStatePostStart: - return o.endpoint.Start(true) - } - return nil -} - -func (o *Outbound) Close() error { - return o.endpoint.Close() -} - -func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - switch network { - case N.NetworkTCP: - o.logger.InfoContext(ctx, "outbound connection to ", destination) - case N.NetworkUDP: - o.logger.InfoContext(ctx, "outbound packet connection to ", destination) - } - if destination.IsFqdn() { - destinationAddresses, err := o.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) - if err != nil { - return nil, err - } - return N.DialSerial(ctx, o.endpoint, network, destination, destinationAddresses) - } else if !destination.Addr.IsValid() { - return nil, E.New("invalid destination: ", destination) - } - return o.endpoint.DialContext(ctx, network, destination) -} - -func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - o.logger.InfoContext(ctx, "outbound packet connection to ", destination) - if destination.IsFqdn() { - destinationAddresses, err := o.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) - if err != nil { - return nil, err - } - packetConn, _, err := N.ListenSerial(ctx, o.endpoint, destination, destinationAddresses) - if err != nil { - return nil, err - } - return packetConn, err - } - return o.endpoint.ListenPacket(ctx, destination) -} - -func (o *Outbound) PreferredDomain(domain string) bool { - return false -} - -func (o *Outbound) PreferredAddress(address netip.Addr) bool { - return o.endpoint.Lookup(address) != nil -} - -func (o *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { - return o.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout) -} diff --git a/route/route.go b/route/route.go index 240d0343..cdd7ba25 100644 --- a/route/route.go +++ b/route/route.go @@ -12,7 +12,6 @@ import ( "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-mux" "github.com/sagernet/sing-tun" @@ -468,37 +467,6 @@ func (r *Router) matchRule( metadata.IPVersion = 6 } - //nolint:staticcheck - if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { - if !preMatch && metadata.InboundOptions.SniffEnabled { - newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{ - OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, - Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), - }, inputConn, inputPacketConn, nil, nil) - if newBuffer != nil { - buffers = []*buf.Buffer{newBuffer} - } else if len(newPackerBuffers) > 0 { - packetBuffers = newPackerBuffers - } - if newErr != nil { - fatalErr = newErr - return - } - } - if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS { - fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{ - Strategy: C.DomainStrategy(metadata.InboundOptions.DomainStrategy), - }) - if fatalErr != nil { - return - } - } - if metadata.InboundOptions.UDPDisableDomainUnmapping { - metadata.UDPDisableDomainUnmapping = true - } - metadata.InboundOptions = option.InboundOptions{} - } - match: for currentRuleIndex, currentRule := range r.rules { metadata.ResetRuleCache() diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index 66a6e5a7..202fb3b3 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -5,7 +5,6 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" @@ -267,14 +266,13 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if len(options.RuleSet) > 0 { + //nolint:staticcheck + if options.Deprecated_RulesetIPCIDRMatchSource { + return nil, E.New("rule_set_ipcidr_match_source is deprecated in sing-box 1.10.0 and removed in sing-box 1.11.0") + } var matchSource bool if options.RuleSetIPCIDRMatchSource { matchSource = true - } else - //nolint:staticcheck - if options.Deprecated_RulesetIPCIDRMatchSource { - matchSource = true - deprecated.Report(ctx, deprecated.OptionBadMatchSource) } item := NewRuleSetItem(router, options.RuleSet, matchSource, false) rule.items = append(rule.items, item) diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index d9570cae..9235dd6f 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -5,7 +5,6 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" @@ -263,14 +262,13 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.RuleSet) > 0 { + //nolint:staticcheck + if options.Deprecated_RulesetIPCIDRMatchSource { + return nil, E.New("rule_set_ipcidr_match_source is deprecated in sing-box 1.10.0 and removed in sing-box 1.11.0") + } var matchSource bool if options.RuleSetIPCIDRMatchSource { matchSource = true - } else - //nolint:staticcheck - if options.Deprecated_RulesetIPCIDRMatchSource { - matchSource = true - deprecated.Report(ctx, deprecated.OptionBadMatchSource) } item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty) rule.items = append(rule.items, item) diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index 605740d4..02354564 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -32,9 +32,6 @@ func TestTUICDomainUDP(t *testing.T) { ListenOptions: option.ListenOptions{ Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - InboundOptions: option.InboundOptions{ - DomainStrategy: option.DomainStrategy(C.DomainStrategyIPv6Only), - }, }, Users: []option.TUICUser{{ UUID: uuid.Nil.String(), diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index 93c283aa..f4043895 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -32,9 +32,7 @@ func TestChainedInbound(t *testing.T) { ListenOptions: option.ListenOptions{ Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - InboundOptions: option.InboundOptions{ - Detour: "detour", - }, + Detour: "detour", }, Method: method, Password: password, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 28cd1da0..6c4b71d4 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -75,10 +75,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool, ListenOptions: option.ListenOptions{ Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - - InboundOptions: option.InboundOptions{ - Detour: "detour", - }, + Detour: "detour", }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ @@ -343,9 +340,7 @@ func TestShadowTLSInbound(t *testing.T) { ListenOptions: option.ListenOptions{ Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())), ListenPort: serverPort, - InboundOptions: option.InboundOptions{ - Detour: "detour", - }, + Detour: "detour", }, Handshake: option.ShadowTLSHandshakeOptions{ ServerOptions: option.ServerOptions{ From 4c65fea1acb10854a092437c8febb3b5a437451b Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:00:41 +0800 Subject: [PATCH 166/185] Fix IPv6 local DNS on Windows --- dns/transport/local/resolv_windows.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dns/transport/local/resolv_windows.go b/dns/transport/local/resolv_windows.go index 76f758c6..04b8d4ef 100644 --- a/dns/transport/local/resolv_windows.go +++ b/dns/transport/local/resolv_windows.go @@ -5,6 +5,7 @@ import ( "net" "net/netip" "os" + "strconv" "syscall" "time" "unsafe" @@ -63,6 +64,9 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig { continue } dnsServerAddr = netip.AddrFrom16(sockaddr.Addr) + if sockaddr.ZoneId != 0 { + dnsServerAddr = dnsServerAddr.WithZone(strconv.FormatInt(int64(sockaddr.ZoneId), 10)) + } default: // Unexpected type. continue From 1beb4cb00234187019d5d64428262ed9e562e26f Mon Sep 17 00:00:00 2001 From: traitman <112139837+traitman@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:13:53 +0800 Subject: [PATCH 167/185] clash-api: Fix websocket connection not closed after config reload via SIGHUP Co-authored-by: TraitMan Co-authored-by: Cursor --- experimental/clashapi/connections.go | 14 ++++++++++---- experimental/clashapi/server.go | 13 +++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/experimental/clashapi/connections.go b/experimental/clashapi/connections.go index 999d5898..5074adf7 100644 --- a/experimental/clashapi/connections.go +++ b/experimental/clashapi/connections.go @@ -2,6 +2,7 @@ package clashapi import ( "bytes" + "context" "net/http" "strconv" "time" @@ -17,15 +18,15 @@ import ( "github.com/gofrs/uuid/v5" ) -func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler { +func connectionRouter(ctx context.Context, router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler { r := chi.NewRouter() - r.Get("/", getConnections(trafficManager)) + r.Get("/", getConnections(ctx, trafficManager)) r.Delete("/", closeAllConnections(router, trafficManager)) r.Delete("/{id}", closeConnection(trafficManager)) return r } -func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { +func getConnections(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Upgrade") != "websocket" { snapshot := trafficManager.Snapshot() @@ -67,7 +68,12 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW tick := time.NewTicker(time.Millisecond * time.Duration(interval)) defer tick.Stop() - for range tick.C { + for { + select { + case <-ctx.Done(): + return + case <-tick.C: + } if err = sendSnapshot(); err != nil { break } diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index e71031dc..c3661182 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -116,12 +116,12 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op r.Use(authentication(options.Secret)) r.Get("/", hello(options.ExternalUI != "")) r.Get("/logs", getLogs(logFactory)) - r.Get("/traffic", traffic(trafficManager)) + r.Get("/traffic", traffic(s.ctx, trafficManager)) r.Get("/version", version) r.Mount("/configs", configRouter(s, logFactory)) r.Mount("/proxies", proxyRouter(s, s.router)) r.Mount("/rules", ruleRouter(s.router)) - r.Mount("/connections", connectionRouter(s.router, trafficManager)) + r.Mount("/connections", connectionRouter(s.ctx, s.router, trafficManager)) r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/script", scriptRouter()) @@ -303,7 +303,7 @@ type Traffic struct { Down int64 `json:"down"` } -func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { +func traffic(ctx context.Context, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var conn net.Conn if r.Header.Get("Upgrade") == "websocket" { @@ -324,7 +324,12 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, defer tick.Stop() buf := &bytes.Buffer{} uploadTotal, downloadTotal := trafficManager.Total() - for range tick.C { + for { + select { + case <-ctx.Done(): + return + case <-tick.C: + } buf.Reset() uploadTotalNew, downloadTotalNew := trafficManager.Total() err := json.NewEncoder(buf).Encode(Traffic{ From 46c6945da51193cecf480695c1c0e05caf1ab2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 1 Mar 2026 18:37:31 +0800 Subject: [PATCH 168/185] documentation: Update mkdcos-material --- Makefile | 4 ++-- mkdocs.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c30cd78f..c96cda95 100644 --- a/Makefile +++ b/Makefile @@ -259,8 +259,8 @@ publish_docs: venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history docs_install: - python -m venv venv - source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*" + python3 -m venv venv + source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.7.2" mkdocs-static-i18n=="1.2.*" clean: rm -rf bin dist sing-box diff --git a/mkdocs.yml b/mkdocs.yml index 7683d376..081ba3aa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,5 @@ site_name: sing-box +site_url: https://sing-box.sagernet.org/ site_author: nekohasekai repo_url: https://github.com/SagerNet/sing-box repo_name: SagerNet/sing-box From ed15121e95a3f8a478040a952a4ef5e53b163d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 1 Mar 2026 19:45:19 +0800 Subject: [PATCH 169/185] sing: Relax domain name validation to support non-standard characters --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0621134c..5f6d926a 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.12 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 - github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07 + github.com/sagernet/sing v0.8.0 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0-beta.13 github.com/sagernet/sing-shadowsocks v0.2.8 From c71abbdfb81bc6af3bf7e9e15234fb8f7d06b8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 1 Mar 2026 20:10:32 +0800 Subject: [PATCH 170/185] Update dependencies --- go.mod | 18 +++++++++--------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 5f6d926a..f9376aeb 100644 --- a/go.mod +++ b/go.mod @@ -35,11 +35,11 @@ require ( github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 github.com/sagernet/sing v0.8.0 github.com/sagernet/sing-mux v0.3.4 - github.com/sagernet/sing-quic v0.6.0-beta.13 + github.com/sagernet/sing-quic v0.6.0 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.0-beta.18 + github.com/sagernet/sing-tun v0.8.1 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 @@ -50,10 +50,10 @@ require ( github.com/vishvananda/netns v0.0.5 go.uber.org/zap v1.27.1 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.46.0 + golang.org/x/crypto v0.48.0 golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 - golang.org/x/mod v0.31.0 - golang.org/x/net v0.48.0 + golang.org/x/mod v0.32.0 + golang.org/x/net v0.50.0 golang.org/x/sys v0.41.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/grpc v1.77.0 @@ -96,7 +96,7 @@ require ( github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/libdns/libdns v1.1.1 // indirect - github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect + github.com/mdlayher/netlink v1.9.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect @@ -156,10 +156,10 @@ require ( go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.40.0 // indirect + golang.org/x/tools v0.41.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect diff --git a/go.sum b/go.sum index 987a87f6..f5d063db 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= -github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= +github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco= +github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= @@ -226,20 +226,20 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07 h1:LQqb+xtR5uqF6bePmJQ3sAToF/kMCjxSnz17HnboXA8= -github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0 h1:OwLEwbcYfZHvu4olZVljxxC1XRicBqJ1HfiFr6F2WEE= +github.com/sagernet/sing v0.8.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.0-beta.13 h1:umDr6GC5fVbOIoTvqV4544wY61zEN+ObQwVGNP8sX1M= -github.com/sagernet/sing-quic v0.6.0-beta.13/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= +github.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM= +github.com/sagernet/sing-quic v0.6.0/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.0-beta.18 h1:C6oHxP9BNBVEVdC9ABMTXmKej9mUVtcuw2v+IiBS8yw= -github.com/sagernet/sing-tun v0.8.0-beta.18/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8= +github.com/sagernet/sing-tun v0.8.1 h1:WtIbDWpSvC6NOGtnK7Lo0HIRbN5a05mmLeIANRniLjE= +github.com/sagernet/sing-tun v0.8.1/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= @@ -323,18 +323,18 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= 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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -349,17 +349,17 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= 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.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 3de56d344e00ca3586af8b2804500770ee76ed7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 2 Mar 2026 06:53:10 +0800 Subject: [PATCH 171/185] Update external dependencies --- go.mod | 28 +++++++++---------- go.sum | 86 ++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/go.mod b/go.mod index f9376aeb..3cf18346 100644 --- a/go.mod +++ b/go.mod @@ -5,24 +5,24 @@ go 1.24.7 require ( github.com/anthropics/anthropic-sdk-go v1.26.0 github.com/anytls/sing-anytls v0.0.11 - github.com/caddyserver/certmagic v0.25.0 + github.com/caddyserver/certmagic v0.25.2 github.com/coder/websocket v1.8.14 github.com/cretz/bine v0.2.0 github.com/database64128/tfo-go/v2 v2.3.2 - github.com/go-chi/chi/v5 v5.2.3 + github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/render v1.0.3 - github.com/godbus/dbus/v5 v5.2.1 + github.com/godbus/dbus/v5 v5.2.2 github.com/gofrs/uuid/v5 v5.4.0 - github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 + github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 github.com/keybase/go-keychain v0.0.1 github.com/libdns/acmedns v0.5.0 - github.com/libdns/alidns v1.0.6-beta.3 + github.com/libdns/alidns v1.0.6 github.com/libdns/cloudflare v0.2.2 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/metacubex/utls v1.8.4 - github.com/mholt/acmez/v3 v3.1.4 - github.com/miekg/dns v1.1.69 - github.com/openai/openai-go/v3 v3.23.0 + github.com/mholt/acmez/v3 v3.1.6 + github.com/miekg/dns v1.1.72 + github.com/openai/openai-go/v3 v3.24.0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a @@ -52,11 +52,11 @@ require ( go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/crypto v0.48.0 golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 - golang.org/x/mod v0.32.0 + golang.org/x/mod v0.33.0 golang.org/x/net v0.50.0 golang.org/x/sys v0.41.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 - google.golang.org/grpc v1.77.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 howett.net/plist v1.0.1 ) @@ -67,7 +67,7 @@ require ( github.com/akutz/memconn v0.1.0 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/andybalholm/brotli v1.1.0 // indirect - github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/caddyserver/zerossl v0.1.5 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect github.com/database64128/netx-go v0.1.1 // indirect @@ -154,15 +154,15 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.42.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index f5d063db..beb2dc92 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= @@ -12,12 +14,14 @@ github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAf github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= -github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic= -github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= +github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= @@ -52,10 +56,12 @@ github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= 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-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -68,8 +74,8 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk= -github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -93,8 +99,8 @@ github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= 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-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU= -github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= +github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 h1:u9i04mGE3iliBh0EFuWaKsmcwrLacqGmq1G3XoaM7gY= +github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= @@ -104,10 +110,14 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= +github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE= github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ= -github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8= -github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= +github.com/libdns/alidns v1.0.6 h1:/Ii428ty6WHFJmE24rZxq2taq++gh7rf9jhgLfp8PmM= +github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= @@ -120,16 +130,16 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= -github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= -github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= -github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/openai/openai-go/v3 v3.23.0 h1:FRFwTcB4FoWFtIunTY/8fgHvzSHgqbfWjiCwOMVrsvw= -github.com/openai/openai-go/v3 v3.23.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= +github.com/openai/openai-go/v3 v3.24.0 h1:08x6GnYiB+AAejTo6yzPY8RkZMJQ8NpreiOyM5QfyYU= +github.com/openai/openai-go/v3 v3.24.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -299,16 +309,16 @@ 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.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -329,14 +339,14 @@ golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1i golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= 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.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= @@ -358,8 +368,8 @@ golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -371,10 +381,10 @@ golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 1803471e02e7c967da3309202ef0b85b0153f5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 2 Mar 2026 11:30:06 +0800 Subject: [PATCH 172/185] endpoint: Fix UDP resolved destination --- common/dialer/dialer.go | 4 +++ protocol/tailscale/endpoint.go | 53 +++++++++++++++++++++++++--------- protocol/wireguard/endpoint.go | 35 ++++++++++++++++------ route/conn.go | 17 +++++++---- 4 files changed, 82 insertions(+), 27 deletions(-) diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index bfa8af21..2ba559f9 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -145,3 +145,7 @@ type ParallelNetworkDialer interface { DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) } + +type PacketDialerWithDestination interface { + ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) +} diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 659277d9..ff82ef86 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -63,6 +63,7 @@ import ( var ( _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) _ adapter.DirectRouteOutbound = (*Endpoint)(nil) + _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) ) func init() { @@ -518,19 +519,7 @@ func (t *Endpoint) DialContext(ctx context.Context, network string, destination } } -func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - t.logger.InfoContext(ctx, "outbound packet connection to ", destination) - if destination.IsFqdn() { - destinationAddresses, err := t.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) - if err != nil { - return nil, err - } - packetConn, _, err := N.ListenSerial(ctx, t, destination, destinationAddresses) - if err != nil { - return nil, err - } - return packetConn, err - } +func (t *Endpoint) listenPacketWithAddress(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { addr4, addr6 := t.server.TailscaleIPs() bind := tcpip.FullAddress{ NIC: 1, @@ -556,6 +545,44 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n return udpConn, nil } +func (t *Endpoint) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) { + t.logger.InfoContext(ctx, "outbound packet connection to ", destination) + if destination.IsFqdn() { + destinationAddresses, err := t.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) + if err != nil { + return nil, netip.Addr{}, err + } + var errors []error + for _, address := range destinationAddresses { + packetConn, packetErr := t.listenPacketWithAddress(ctx, M.SocksaddrFrom(address, destination.Port)) + if packetErr == nil { + return packetConn, address, nil + } + errors = append(errors, packetErr) + } + return nil, netip.Addr{}, E.Errors(errors...) + } + packetConn, err := t.listenPacketWithAddress(ctx, destination) + if err != nil { + return nil, netip.Addr{}, err + } + if destination.IsIP() { + return packetConn, destination.Addr, nil + } + return packetConn, netip.Addr{}, nil +} + +func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + packetConn, destinationAddress, err := t.ListenPacketWithDestination(ctx, destination) + if err != nil { + return nil, err + } + if destinationAddress.IsValid() && destination != M.SocksaddrFrom(destinationAddress, destination.Port) { + return bufio.NewNATPacketConn(bufio.NewPacketConn(packetConn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil + } + return packetConn, nil +} + func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) { tsFilter := t.filter.Load() if tsFilter != nil { diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 35ffd19e..bcf2078e 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -24,7 +24,10 @@ import ( "github.com/sagernet/sing/service" ) -var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) +var ( + _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil) + _ dialer.PacketDialerWithDestination = (*Endpoint)(nil) +) func RegisterEndpoint(registry *endpoint.Registry) { endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint) @@ -219,20 +222,34 @@ func (w *Endpoint) DialContext(ctx context.Context, network string, destination return w.endpoint.DialContext(ctx, network, destination) } -func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *Endpoint) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) { w.logger.InfoContext(ctx, "outbound packet connection to ", destination) if destination.IsFqdn() { destinationAddresses, err := w.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{}) if err != nil { - return nil, err + return nil, netip.Addr{}, err } - packetConn, _, err := N.ListenSerial(ctx, w.endpoint, destination, destinationAddresses) - if err != nil { - return nil, err - } - return packetConn, err + return N.ListenSerial(ctx, w.endpoint, destination, destinationAddresses) } - return w.endpoint.ListenPacket(ctx, destination) + packetConn, err := w.endpoint.ListenPacket(ctx, destination) + if err != nil { + return nil, netip.Addr{}, err + } + if destination.IsIP() { + return packetConn, destination.Addr, nil + } + return packetConn, netip.Addr{}, nil +} + +func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + packetConn, destinationAddress, err := w.ListenPacketWithDestination(ctx, destination) + if err != nil { + return nil, err + } + if destinationAddress.IsValid() && destination != M.SocksaddrFrom(destinationAddress, destination.Port) { + return bufio.NewNATPacketConn(bufio.NewPacketConn(packetConn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil + } + return packetConn, nil } func (w *Endpoint) PreferredDomain(domain string) bool { diff --git a/route/conn.go b/route/conn.go index 899d2939..59afe539 100644 --- a/route/conn.go +++ b/route/conn.go @@ -188,6 +188,8 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial } else { if len(metadata.DestinationAddresses) > 0 { remotePacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) + } else if packetDialer, withDestination := this.(dialer.PacketDialerWithDestination); withDestination { + remotePacketConn, destinationAddress, err = packetDialer.ListenPacketWithDestination(ctx, metadata.Destination) } else { remotePacketConn, err = this.ListenPacket(ctx, metadata.Destination) } @@ -218,11 +220,16 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial } if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded { natConn.UpdateDestination(destinationAddress) - } else if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) { - if metadata.UDPDisableDomainUnmapping { - remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) - } else { - remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination) + } else { + destination := M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) + if metadata.Destination != destination { + if metadata.UDPDisableDomainUnmapping { + remotePacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(remotePacketConn), destination, originDestination) + } else { + remotePacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(remotePacketConn), destination, originDestination) + } + } else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination { + remotePacketConn = bufio.NewDestinationNATPacketConn(bufio.NewPacketConn(remotePacketConn), metadata.Destination, metadata.RouteOriginalDestination) } } } else if metadata.RouteOriginalDestination.IsValid() && metadata.RouteOriginalDestination != metadata.Destination { From 91f92bee4971ca5d821014e9197039232880f5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 2 Mar 2026 13:19:57 +0800 Subject: [PATCH 173/185] release: Unify default build tags and linker flags into shared files Move hardcoded build tags and ldflags from Makefile, Dockerfile, CI workflows, and local build scripts into canonical files under release/: - release/DEFAULT_BUILD_TAGS (Linux common archs, Darwin, Android) - release/DEFAULT_BUILD_TAGS_WINDOWS (includes with_purego) - release/DEFAULT_BUILD_TAGS_OTHERS (no with_naive_outbound) - release/LDFLAGS (shared linker flags) --- .github/workflows/build.yml | 40 +++++++++++++++-------- .github/workflows/docker.yml | 12 ++++--- .github/workflows/linux.yml | 12 ++++--- .golangci.yml | 5 +++ Dockerfile | 7 ++-- Makefile | 5 +-- docs/installation/build-from-source.md | 28 ++++++++++++++-- docs/installation/build-from-source.zh.md | 28 ++++++++++++++-- release/DEFAULT_BUILD_TAGS | 1 + release/DEFAULT_BUILD_TAGS_OTHERS | 1 + release/DEFAULT_BUILD_TAGS_WINDOWS | 1 + release/LDFLAGS | 1 + release/local/common.sh | 6 ++-- 13 files changed, 114 insertions(+), 33 deletions(-) create mode 100644 release/DEFAULT_BUILD_TAGS create mode 100644 release/DEFAULT_BUILD_TAGS_OTHERS create mode 100644 release/DEFAULT_BUILD_TAGS_WINDOWS create mode 100644 release/LDFLAGS diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 788b20af..2bd03547 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -207,9 +207,10 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then - TAGS="${TAGS},with_naive_outbound" + TAGS=$(cat release/DEFAULT_BUILD_TAGS) + else + TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) fi if [[ "${{ matrix.variant }}" == "purego" ]]; then TAGS="${TAGS},with_purego" @@ -217,13 +218,16 @@ jobs: TAGS="${TAGS},with_musl" fi echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Set shared ldflags + run: | + echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" - name: Build (purego) if: matrix.variant == 'purego' run: | set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "0" @@ -245,7 +249,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -262,7 +266,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -279,7 +283,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "0" @@ -299,7 +303,7 @@ jobs: export CXX="${CC}++" mkdir -p dist GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -452,17 +456,21 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then - TAGS="${TAGS},with_naive_outbound" + TAGS=$(cat release/DEFAULT_BUILD_TAGS) + else + TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) fi echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Set shared ldflags + run: | + echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" - name: Build run: | set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -520,9 +528,11 @@ jobs: - name: Build if: matrix.naive run: | + $TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS + $LDFLAGS_SHARED = Get-Content release/LDFLAGS mkdir -p dist - go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` - -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" ` + go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" ` + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" ` ./cmd/sing-box env: CGO_ENABLED: "0" @@ -532,9 +542,11 @@ jobs: - name: Build if: ${{ !matrix.naive }} run: | + $TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS + $LDFLAGS_SHARED = Get-Content release/LDFLAGS mkdir -p dist - go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" ` - -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" ` + go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" ` + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" ` ./cmd/sing-box env: CGO_ENABLED: "0" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0cb256fd..86775310 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -104,17 +104,21 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then - TAGS="${TAGS},with_naive_outbound,with_musl" + TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl" + else + TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) fi echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Set shared ldflags + run: | + echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" - name: Build (naive) if: matrix.naive run: | set -xeuo pipefail go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -127,7 +131,7 @@ jobs: run: | set -xeuo pipefail go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "0" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 414d02e5..1238902a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -125,18 +125,22 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then - TAGS="${TAGS},with_naive_outbound,with_musl" + TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl" + else + TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) fi echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" + - name: Set shared ldflags + run: | + echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" - name: Build (naive) if: matrix.naive run: | set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "1" @@ -152,7 +156,7 @@ jobs: set -xeuo pipefail mkdir -p dist go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ - -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \ + -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ ./cmd/sing-box env: CGO_ENABLED: "0" diff --git a/.golangci.yml b/.golangci.yml index 9a20700a..d6905dc1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,6 +9,11 @@ run: - with_utls - with_acme - with_clash_api + - with_tailscale + - with_ccm + - with_ocm + - badlinkname + - tfogo_checklinkname0 linters: default: none enable: diff --git a/Dockerfile b/Dockerfile index 8589b331..c8600d57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,10 +12,11 @@ RUN set -ex \ && apk add git build-base \ && export COMMIT=$(git rev-parse --short HEAD) \ && export VERSION=$(go run ./cmd/internal/read_tag) \ - && go build -v -trimpath -tags \ - "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \ + && export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \ + && export LDFLAGS_SHARED=$(cat release/LDFLAGS) \ + && go build -v -trimpath -tags "$TAGS" \ -o /go/bin/sing-box \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \ + -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" $LDFLAGS_SHARED -s -w -buildid=" \ ./cmd/sing-box FROM --platform=$TARGETPLATFORM alpine AS dist LABEL maintainer="nekohasekai " diff --git a/Makefile b/Makefile index c96cda95..1a1138cc 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,13 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0 +TAGS ?= $(shell cat release/DEFAULT_BUILD_TAGS_OTHERS) GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" +LDFLAGS_SHARED = $(shell cat release/LDFLAGS) +PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' $(LDFLAGS_SHARED) -s -w -buildid=" MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)" MAIN = ./cmd/sing-box PREFIX ?= $(shell go env GOPATH) diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index 4d0e6370..552ec3fe 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -57,11 +57,35 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | | `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | | `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | -| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) | -| `with_naive_outbound` | :material-close:️ | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/). | +| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale). | +| `with_ccm` | :material-check: | Build with Claude Code Multiplexer service support. | +| `with_ocm` | :material-check: | Build with OpenAI Codex Multiplexer service support. | +| `with_naive_outbound` | :material-check: | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/). | +| `badlinkname` | :material-check: | Enable `go:linkname` access to internal standard library functions. Required because the Go standard library does not expose many low-level APIs needed by this project, and reimplementing them externally is impractical. Used for kTLS (kernel TLS offload) and raw TLS record manipulation. | +| `tfogo_checklinkname0` | :material-check: | Companion to `badlinkname`. Go 1.23+ enforces `go:linkname` restrictions via the linker; this tag signals the build uses `-checklinkname=0` to bypass that enforcement. | It is not recommended to change the default build tag list unless you really know what you are adding. +## :material-wrench: Linker Flags + +The following `-ldflags` are used in official builds: + +| Flag | Description | +|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `-X 'internal/godebug.defaultGODEBUG=multipathtcp=0'` | Go 1.24 enabled Multipath TCP for listeners by default (`multipathtcp=2`). This may cause errors on low-level sockets, and sing-box has its own MPTCP control (`tcp_multi_path` option). This flag disables the Go default. | +| `-checklinkname=0` | Go 1.23+ linker rejects unauthorized `go:linkname` usage. This flag disables the check, required together with the `badlinkname` build tag. | + +## :material-package-variant: For Downstream Packagers + +The default build tag lists and linker flags are available as files in the repository for downstream packagers to reference directly: + +| File | Description | +|------|-------------| +| `release/DEFAULT_BUILD_TAGS` | Default for Linux (common architectures), Darwin, and Android. | +| `release/DEFAULT_BUILD_TAGS_WINDOWS` | Default for Windows (includes `with_purego`). | +| `release/DEFAULT_BUILD_TAGS_OTHERS` | Default for other platforms (no `with_naive_outbound`). | +| `release/LDFLAGS` | Required linker flags (see above). | + ## :material-layers: with_naive_outbound NaiveProxy outbound requires special build configurations depending on your target platform. diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index 70434f03..0baf63c3 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -61,11 +61,35 @@ go build -tags "tag_a tag_b" ./cmd/sing-box | `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | | `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | | `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | -| `with_tailscale` | :material-check: | Build with Tailscale support, see [Tailscale endpoint](/configuration/endpoint/tailscale) | -| `with_naive_outbound` | :material-close:️ | 构建 NaiveProxy 出站支持,参阅 [NaiveProxy 出站](/zh/configuration/outbound/naive/)。 | +| `with_tailscale` | :material-check: | 构建 Tailscale 支持,参阅 [Tailscale 端点](/configuration/endpoint/tailscale)。 | +| `with_ccm` | :material-check: | 构建 Claude Code Multiplexer 服务支持。 | +| `with_ocm` | :material-check: | 构建 OpenAI Codex Multiplexer 服务支持。 | +| `with_naive_outbound` | :material-check: | 构建 NaiveProxy 出站支持,参阅 [NaiveProxy 出站](/configuration/outbound/naive/)。 | +| `badlinkname` | :material-check: | 启用 `go:linkname` 以访问标准库内部函数。Go 标准库未提供本项目需要的许多底层 API,且在外部重新实现不切实际。用于 kTLS(内核 TLS 卸载)和原始 TLS 记录操作。 | +| `tfogo_checklinkname0` | :material-check: | `badlinkname` 的伴随标记。Go 1.23+ 链接器强制限制 `go:linkname` 使用;此标记表示构建使用 `-checklinkname=0` 以绕过该限制。 | 除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。 +## :material-wrench: 链接器标志 + +以下 `-ldflags` 在官方构建中使用: + +| 标志 | 说明 | +|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `-X 'internal/godebug.defaultGODEBUG=multipathtcp=0'` | Go 1.24 默认为监听器启用 Multipath TCP(`multipathtcp=2`)。这可能在底层 socket 上导致错误,且 sing-box 有自己的 MPTCP 控制(`tcp_multi_path` 选项)。此标志禁用 Go 的默认行为。 | +| `-checklinkname=0` | Go 1.23+ 链接器拒绝未授权的 `go:linkname` 使用。此标志禁用该检查,需要与 `badlinkname` 构建标记一起使用。 | + +## :material-package-variant: 下游打包者 + +默认构建标签列表和链接器标志以文件形式存放在仓库中,供下游打包者直接引用: + +| 文件 | 说明 | +|------|------| +| `release/DEFAULT_BUILD_TAGS` | Linux(常见架构)、Darwin 和 Android 的默认标签。 | +| `release/DEFAULT_BUILD_TAGS_WINDOWS` | Windows 的默认标签(包含 `with_purego`)。 | +| `release/DEFAULT_BUILD_TAGS_OTHERS` | 其他平台的默认标签(不含 `with_naive_outbound`)。 | +| `release/LDFLAGS` | 必需的链接器标志(参见上文)。 | + ## :material-layers: with_naive_outbound NaiveProxy 出站需要根据目标平台进行特殊的构建配置。 diff --git a/release/DEFAULT_BUILD_TAGS b/release/DEFAULT_BUILD_TAGS new file mode 100644 index 00000000..4374ea93 --- /dev/null +++ b/release/DEFAULT_BUILD_TAGS @@ -0,0 +1 @@ +with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,badlinkname,tfogo_checklinkname0 \ No newline at end of file diff --git a/release/DEFAULT_BUILD_TAGS_OTHERS b/release/DEFAULT_BUILD_TAGS_OTHERS new file mode 100644 index 00000000..814b53f0 --- /dev/null +++ b/release/DEFAULT_BUILD_TAGS_OTHERS @@ -0,0 +1 @@ +with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0 \ No newline at end of file diff --git a/release/DEFAULT_BUILD_TAGS_WINDOWS b/release/DEFAULT_BUILD_TAGS_WINDOWS new file mode 100644 index 00000000..746827a7 --- /dev/null +++ b/release/DEFAULT_BUILD_TAGS_WINDOWS @@ -0,0 +1 @@ +with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0 \ No newline at end of file diff --git a/release/LDFLAGS b/release/LDFLAGS new file mode 100644 index 00000000..8f613f97 --- /dev/null +++ b/release/LDFLAGS @@ -0,0 +1 @@ +-X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0 \ No newline at end of file diff --git a/release/local/common.sh b/release/local/common.sh index b1fd367c..13a8415c 100755 --- a/release/local/common.sh +++ b/release/local/common.sh @@ -11,7 +11,7 @@ INSTALL_CONFIG_PATH="/usr/local/etc/sing-box" INSTALL_DATA_PATH="/var/lib/sing-box" SYSTEMD_SERVICE_PATH="/etc/systemd/system" -DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" +DEFAULT_BUILD_TAGS="$(cat "$PROJECT_DIR/release/DEFAULT_BUILD_TAGS_OTHERS")" setup_environment() { if [ -d /usr/local/go ]; then @@ -44,7 +44,9 @@ get_version() { get_ldflags() { local version version=$(get_version) - echo "-X 'github.com/sagernet/sing-box/constant.Version=${version}' -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" + local shared_ldflags + shared_ldflags=$(cat "$PROJECT_DIR/release/LDFLAGS") + echo "-X 'github.com/sagernet/sing-box/constant.Version=${version}' ${shared_ldflags} -s -w -buildid=" } build_sing_box() { From 96c5c276102d4be0ae828458e2a210b98d15202a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Mar 2026 13:49:58 +0800 Subject: [PATCH 174/185] sing: reject IP literals in IsDomainName --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3cf18346..2575da8c 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.12 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 - github.com/sagernet/sing v0.8.0 + github.com/sagernet/sing v0.8.1 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index beb2dc92..bee0d80b 100644 --- a/go.sum +++ b/go.sum @@ -236,8 +236,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.0 h1:OwLEwbcYfZHvu4olZVljxxC1XRicBqJ1HfiFr6F2WEE= -github.com/sagernet/sing v0.8.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.1 h1:Li+zg4xdiMsvdX4j50TPqmSG8LF/TB9US2qlAN40izU= +github.com/sagernet/sing v0.8.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM= From d14417d3929c1e32d22510387e8b5a5c48b40135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Mar 2026 18:24:25 +0800 Subject: [PATCH 175/185] Fix naive client close --- .github/CRONET_GO_VERSION | 2 +- go.mod | 62 +++++++++---------- go.sum | 124 +++++++++++++++++++------------------- 3 files changed, 94 insertions(+), 94 deletions(-) diff --git a/.github/CRONET_GO_VERSION b/.github/CRONET_GO_VERSION index e438ee5d..2838ee07 100644 --- a/.github/CRONET_GO_VERSION +++ b/.github/CRONET_GO_VERSION @@ -1 +1 @@ -17c7ef18afa63b205e835c6270277b29382eb8e3 +cba7b9ac0399055aa49fbdc57c03c374f58e1597 diff --git a/go.mod b/go.mod index 2575da8c..75effd42 100644 --- a/go.mod +++ b/go.mod @@ -27,8 +27,8 @@ require ( github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6 - github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6 + github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399 + github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.12 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -105,35 +105,35 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.9 // indirect diff --git a/go.sum b/go.sum index bee0d80b..a04691c3 100644 --- a/go.sum +++ b/go.sum @@ -162,68 +162,68 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6 h1:Ato+guxmEL4uezcYV1UUUDpAv9HlcJQ7BZt2zpnzjuw= -github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= -github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6 h1:0ldSjcR5Gt/o+otTvUAmJ28FCLab9lnlpEhxRCMQpRA= -github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6/go.mod h1:xVwYoNCyv9tF7W1RJlUdDbT4bn5tyqtyTe1P1ZY2VP8= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d h1:tudlBYdQHIWctKIdf7pceBOFIUIISK6yFivwsxhxDk0= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d h1:F5EsQlIknj0HlExBFR4EXW69dYj0MpK1HCpKhL/weEs= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d h1:9SQ6I2Y2radd6RyWEfV+9s1Q9Kth54B6gBHuJWNzQas= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d h1:+XoeknBi6+s6epDAS3BkEsp5zGqEJsT9L8JEcaq+0nE= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d h1:poqfhHJAg+7BtABn4cue7V4y8Kb2eZ1Cy0j+bhDangw= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d h1:nH6rtfqWbui9zQPjd18cpvZncGvn21UcVLtmeUoQKXs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d h1:HtnjWZzSQBaP29XJ5NoIps1TVZ7DUC7R0NH7IyhJ5Ag= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d h1:E2DWx0Agrj8Fi745S+otYW+W0rL2I8+Z2rZCFqGYPvQ= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d h1:j7f/rBwPlO1RpFQeM35QVHymVXGVo6d8WTz4i4SjcPo= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d h1:hz8kkcHGMe7QBTpbqkaw89ZFsfX+UN5F5RIDrroDkx8= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d h1:TNFaO19ySEyqG79j5+dYb+w4ivusrTXanWuogmC4VM0= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d h1:Ewc/wR3yu/hOwG/p49nI9TwYmYv3Llm5DA6fSb1w8hY= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d h1:PJ24NkPNpMrLGNRdb6moEqJo8gfhYcIRZmQD8jPPCJk= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d h1:IaUghNA8cOmmwvzUPKPsfhiG0KmpWpE0mFZl85T5/Bw= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d h1:whbeDcr9dDWPr45Is9QV6OHAncrBWLJtPuo4uyEJFBg= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d h1:ecHgaGMvikNYjsfULekdXjL/cQJXCS38yvHaKVMWtXc= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d h1:no7Cb54+vv1bQ39zFp+JIHKO8Tu3sTwqz8SoOAuV/Ek= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d h1:DqBSbam9KAzBgDInOoNy4K0baSJyxGWESxrDewU5aSs= -github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= -github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d h1:fOR5i+hRyjG8ZzPSG6URkoTKr5qYOJfxZ58zd8HBteM= -github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= -github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d h1:hEQGQI+PfUzYBVas4NWw8WiEUsATco6vwv+t4qTtgtw= -github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= -github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d h1:AzzJ5AtZlwTbU5QOSixZdPLTjzWKCun3AobQChKy0W8= -github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= -github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d h1:9Tp7s/WX4DZLx4ues8G38G2OV7eQbeuU2COEZEbGcF0= -github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= -github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d h1:T9EVZKTyZHOamwevomUZnJ6TQNc09I/BwK+L5HJCJj8= -github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= -github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d h1:FZmThI7xScJRPERFiA4L2l9KCwA0oi8/lEOajIKEtUQ= -github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d h1:BCC/b8bL0dD9Q4ghgKABV/EsMe0J8GE/l7hcRdPkUXQ= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d h1:3l463BXnC/X42ow2zqHm9Y/K4GM6aRsKUIZBcFxr2+Q= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d h1:+XHEZ/z5NgPfjOAzOwfbQzR+42qaDNB0nv+fAOcd6Pc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d h1:sYWbP+qCt9Rhb1yGaIRY7HVLtaQZmrHWR0obc5+Q1qc= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d h1:r6eOVlAfmcUMD5nfz+mPd/aORevUKhcvxA1z1GdPnG8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399 h1:x3tVYQHdqqnKbEd9/H4KIGhtHTjA+KfiiaXedI3/w8Q= +github.com/sagernet/cronet-go v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw= +github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399 h1:mD3ehudpYf1IFgCTv25d/B6KnBc/lLFq1jmSQIK24y0= +github.com/sagernet/cronet-go/all v0.0.0-20260303101018-cba7b9ac0399/go.mod h1:MbYagcGGIaRo9tNrgafbCTO+Qc7eVEh32ZWMprSB8b0= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6 h1:ghRKgSaswefPwQF8AYtUlNyumILOB0ptJWxgZ8MFrEE= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:Behr7YCnQP2dsvzAJDIoMd5nTVU9/d6MMtk/S3MctwA= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6 h1:6UL9XdGU/44oTHj36e+EBDJ0RonFoObmd299NG/qQCU= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:Q9apxjtkj6iMIBQlTo71QsOTrNlhHneaXQb1Q0IshU8= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:0N+xlnMkFEeqgFe3X/PEvHt+/t+BPgxmbx7wzNcYppg= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:7f2vTXtePikBSV1bdD0zs5/WuZM+bRuej3mREpWL/qQ= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:HMlnhEYs+axOa0tAJ79se3QsYB8CpRCQo9mewWWFeeg= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:Ux/U6vF+1AoGLSJK3jVa9Kqkn64MX4Ivv7fy0ikDrpQ= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:5Dhuere2bQFzfGvKxA7TFgA5MoTtgcZMmJQuKwQKlyA= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6 h1:aMRcLow4UpZWZ28fR9FjveTL/4okrigZySIkEVZnlgA= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6 h1:y4g8oNtEfSdcKrBKsH5vMAjzGthvhHFNU80sanYDQEM= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:CXN6OPILi5trwffmYiiJ9rqJL3XAWx1menLrBBwA0gU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:ZphFHQeFOTpqCWPwFcQRnrePXajml8LbKlYFJ5n0isU= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6 h1:nKzFK84oANHz7I6bab+25bBY+pdpAbO0b3NJroyLldo= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:HqqZUGRXcWvvwlbuvjk/efo8TKW1H/aHdqQTde+Xs9Q= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:D2v9lZZG5sm4x/CkG7uqc6ZU3YlhFQ+GmJfvZMK0h/s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6 h1:TWveNeXHrA5r8XOlf+vw7U2b2M0ip6GNF89jcUi1ogw= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6 h1:DVCBoXOZI4PNG0cbCLg8lrphRXoLFcAIDLNmzsCVg3I= +github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:7s5xqNlBUWkIXdruPYi3/txXekQhGWxrYxbnB0cnARo= +github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6 h1:eyEb+Q7VH4hpE1nV+EmEnN2XX5WilgBpIsfCw4C/7no= +github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6 h1:9F1W7+z1hHST6GSzdpQ8Q0NCkneAL18dkRA1HfxH09A= +github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6 h1:MmQIR3iJsdvw1ONBP3geK57i9c3+v9dXPMNdZYcYGKw= +github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6 h1:j6Pk1Wsl+PCbKRXtp7a912D2D6zqX5Nk51wDQU9TEDc= +github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6 h1:0DnFhbRfNqwguNCxiinA7BowQ/RaFt627sjW09JNp80= +github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:3CZmlEk2/WW5UHLFJZxXPJ9IJxX3td8U3PyqWSGMl3c= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:eHkVRptoZf3BuuskkjcclO2dwQrX4zluoVGODMrX7n0= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6 h1:UgFmE0cZo9euu8/7sTAhj1G8lldavwXBdcPNyTE29CQ= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6 h1:xbg3ZB9tLMGDQe4+aewG0Z4bEP/2pLtYBcDzILv5eEc= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 h1:M0bTSTSTnSMlPY2WaZT6fL5TFICqk8v4cm+QVf8Fcao= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg= From ab76062a4141808a33c07189c46831681d891bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Mar 2026 21:36:29 +0800 Subject: [PATCH 176/185] Fix fake-ip address allocation --- dns/transport/fakeip/store.go | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/dns/transport/fakeip/store.go b/dns/transport/fakeip/store.go index 4c09ed7a..b7c51dfa 100644 --- a/dns/transport/fakeip/store.go +++ b/dns/transport/fakeip/store.go @@ -18,6 +18,8 @@ type Store struct { logger logger.Logger inet4Range netip.Prefix inet6Range netip.Prefix + inet4Last netip.Addr + inet6Last netip.Addr storage adapter.FakeIPStorage addressAccess sync.Mutex @@ -26,12 +28,35 @@ type Store struct { } func NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store { - return &Store{ + store := &Store{ ctx: ctx, logger: logger, inet4Range: inet4Range, inet6Range: inet6Range, } + if inet4Range.IsValid() { + store.inet4Last = broadcastAddress(inet4Range) + } + if inet6Range.IsValid() { + store.inet6Last = broadcastAddress(inet6Range) + } + return store +} + +func broadcastAddress(prefix netip.Prefix) netip.Addr { + addr := prefix.Addr() + raw := addr.As16() + bits := prefix.Bits() + if addr.Is4() { + bits += 96 + } + for i := bits; i < 128; i++ { + raw[i/8] |= 1 << (7 - i%8) + } + if addr.Is4() { + return netip.AddrFrom4([4]byte(raw[12:])) + } + return netip.AddrFrom16(raw) } func (s *Store) Start() error { @@ -49,10 +74,10 @@ func (s *Store) Start() error { s.inet6Current = metadata.Inet6Current } else { if s.inet4Range.IsValid() { - s.inet4Current = s.inet4Range.Addr().Next().Next() + s.inet4Current = s.inet4Range.Addr().Next() } if s.inet6Range.IsValid() { - s.inet6Current = s.inet6Range.Addr().Next().Next() + s.inet6Current = s.inet6Range.Addr().Next() } _ = storage.FakeIPReset() } @@ -98,7 +123,7 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { return netip.Addr{}, E.New("missing IPv4 fakeip address range") } nextAddress := s.inet4Current.Next() - if !s.inet4Range.Contains(nextAddress) { + if nextAddress == s.inet4Last || !s.inet4Range.Contains(nextAddress) { nextAddress = s.inet4Range.Addr().Next().Next() } s.inet4Current = nextAddress @@ -108,7 +133,7 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { return netip.Addr{}, E.New("missing IPv6 fakeip address range") } nextAddress := s.inet6Current.Next() - if !s.inet6Range.Contains(nextAddress) { + if nextAddress == s.inet6Last || !s.inet6Range.Contains(nextAddress) { nextAddress = s.inet6Range.Addr().Next().Next() } s.inet6Current = nextAddress From f295e195b5ac266eba77fdbabb31f3241a349840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Mar 2026 22:06:54 +0800 Subject: [PATCH 177/185] tailscale: Fix netstack TCP connections with system interface --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 75effd42..08f652b4 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/sagernet/sing-tun v0.8.1 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260303140313-3bcf9a4b9349 github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index a04691c3..a669812d 100644 --- a/go.sum +++ b/go.sum @@ -254,8 +254,8 @@ github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkV github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260303140313-3bcf9a4b9349 h1:ju7aTbndj2sqK4NplE97ynLdhuCtel5OS4e0NrT71nk= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260303140313-3bcf9a4b9349/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:f9cXNB+IOOPnR8DOLMTpr42jf7naxh5Un5Y09BBf5Cg= github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= From e62dc7bfa28beda8fdd93f61102976ccf7812a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Mar 2026 23:26:05 +0800 Subject: [PATCH 178/185] Fix rule_set_ip_cidr_accept_empty not working --- dns/client.go | 4 +++- dns/router.go | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/dns/client.go b/dns/client.go index 2982d11c..ed4e8207 100644 --- a/dns/client.go +++ b/dns/client.go @@ -240,8 +240,10 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m if responseChecker != nil { var rejected bool // TODO: add accept_any rule and support to check response instead of addresses - if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 { + if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { rejected = true + } else if len(response.Answer) == 0 { + rejected = !responseChecker(nil) } else { rejected = !responseChecker(MessageToAddresses(response)) } diff --git a/dns/router.go b/dns/router.go index 18b9e34d..567f3225 100644 --- a/dns/router.go +++ b/dns/router.go @@ -272,13 +272,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte return action.Response(message), nil } } - var responseCheck func(responseAddrs []netip.Addr) bool - if rule != nil && rule.WithAddressLimit() { - responseCheck = func(responseAddrs []netip.Addr) bool { - metadata.DestinationAddresses = responseAddrs - return rule.MatchAddressLimit(metadata) - } - } + responseCheck := addressLimitResponseCheck(rule, metadata) if dnsOptions.Strategy == C.DomainStrategyAsIS { dnsOptions.Strategy = r.defaultDomainStrategy } @@ -394,13 +388,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ goto response } } - var responseCheck func(responseAddrs []netip.Addr) bool - if rule != nil && rule.WithAddressLimit() { - responseCheck = func(responseAddrs []netip.Addr) bool { - metadata.DestinationAddresses = responseAddrs - return rule.MatchAddressLimit(metadata) - } - } + responseCheck := addressLimitResponseCheck(rule, metadata) if dnsOptions.Strategy == C.DomainStrategyAsIS { dnsOptions.Strategy = r.defaultDomainStrategy } @@ -428,6 +416,18 @@ func isAddressQuery(message *mDNS.Msg) bool { return false } +func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(responseAddrs []netip.Addr) bool { + if rule == nil || !rule.WithAddressLimit() { + return nil + } + responseMetadata := *metadata + return func(responseAddrs []netip.Addr) bool { + checkMetadata := responseMetadata + checkMetadata.DestinationAddresses = responseAddrs + return rule.MatchAddressLimit(&checkMetadata) + } +} + func (r *Router) ClearCache() { r.client.ClearCache() if r.platformInterface != nil { From fb269c90326deea9af13e170e962efe51417e383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 5 Mar 2026 20:38:19 +0800 Subject: [PATCH 179/185] tun: Fix darwin batch loop not exit on EBADF --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 08f652b4..99f66670 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 - github.com/sagernet/sing-tun v0.8.1 + github.com/sagernet/sing-tun v0.8.2 github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6.0.20260303140313-3bcf9a4b9349 diff --git a/go.sum b/go.sum index a669812d..b2f898b1 100644 --- a/go.sum +++ b/go.sum @@ -248,8 +248,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.1 h1:WtIbDWpSvC6NOGtnK7Lo0HIRbN5a05mmLeIANRniLjE= -github.com/sagernet/sing-tun v0.8.1/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs= +github.com/sagernet/sing-tun v0.8.2 h1:rQr/x3eQCHh3oleIaoJdPdJwqzZp4+QWcJLT0Wz2xKY= +github.com/sagernet/sing-tun v0.8.2/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= From 88695b0d1f64cf5e7a1fd17ea5fd2ccfbd600255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 5 Mar 2026 21:10:45 +0800 Subject: [PATCH 180/185] Rename branches and update release workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stable-next → oldstable, main-next → stable, dev-next → testing, new unstable --- .github/renovate.json | 2 +- .github/workflows/build.yml | 23 ++++++++++++----------- .github/workflows/docker.yml | 5 +++-- .github/workflows/lint.yml | 14 ++++++++------ .github/workflows/linux.yml | 5 +++-- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 78d9c961..e24ff248 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -6,7 +6,7 @@ ":disableRateLimiting" ], "baseBranches": [ - "dev-next" + "unstable" ], "golang": { "enabled": false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bd03547..3ebb2ca9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,8 +25,9 @@ on: - publish-android push: branches: - - main-next - - dev-next + - stable + - testing + - unstable concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }} @@ -591,7 +592,7 @@ jobs: path: "dist" build_android: name: Build Android - if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' + if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android') && github.ref != 'refs/heads/oldstable' runs-on: ubuntu-latest needs: - calculate_version @@ -627,12 +628,12 @@ jobs: JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - name: Checkout main branch - if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' + if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch' run: |- cd clients/android git checkout main - name: Checkout dev branch - if: github.ref == 'refs/heads/dev-next' + if: github.ref == 'refs/heads/testing' run: |- cd clients/android git checkout dev @@ -681,7 +682,7 @@ jobs: path: 'dist' publish_android: name: Publish Android - if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' + if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' && github.ref != 'refs/heads/oldstable' runs-on: ubuntu-latest needs: - calculate_version @@ -717,12 +718,12 @@ jobs: JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - name: Checkout main branch - if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' + if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch' run: |- cd clients/android git checkout main - name: Checkout dev branch - if: github.ref == 'refs/heads/dev-next' + if: github.ref == 'refs/heads/testing' run: |- cd clients/android git checkout dev @@ -801,12 +802,12 @@ jobs: git tag v${{ needs.calculate_version.outputs.version }} -f echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" - name: Checkout main branch - if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch' + if: matrix.if && github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch' run: |- cd clients/apple git checkout main - name: Checkout dev branch - if: matrix.if && github.ref == 'refs/heads/dev-next' + if: matrix.if && github.ref == 'refs/heads/testing' run: |- cd clients/apple git checkout dev @@ -892,7 +893,7 @@ jobs: -authenticationKeyID $ASC_KEY_ID \ -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID - name: Publish to TestFlight - if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next' + if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/testing' run: |- go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }} - name: Build image diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 86775310..75e32583 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,8 +3,8 @@ name: Publish Docker Images on: #push: # branches: - # - main-next - # - dev-next + # - stable + # - testing release: types: - published @@ -19,6 +19,7 @@ env: jobs: build_binary: name: Build binary + if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable' runs-on: ubuntu-latest strategy: fail-fast: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e1485b38..2e86bb62 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,18 +3,20 @@ name: Lint on: push: branches: - - stable-next - - main-next - - dev-next + - oldstable + - stable + - testing + - unstable paths-ignore: - '**.md' - '.github/**' - '!.github/workflows/lint.yml' pull_request: branches: - - stable-next - - main-next - - dev-next + - oldstable + - stable + - testing + - unstable jobs: build: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1238902a..a029329c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -3,8 +3,8 @@ name: Build Linux Packages on: #push: # branches: - # - main-next - # - dev-next + # - stable + # - testing workflow_dispatch: inputs: version: @@ -23,6 +23,7 @@ on: jobs: calculate_version: name: Calculate version + if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable' runs-on: ubuntu-latest outputs: version: ${{ steps.outputs.outputs.version }} From 7fd21f8bf4592303160cc45cbd0dd38a0cdcb512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Mar 2026 13:52:24 +0800 Subject: [PATCH 181/185] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/clients/android b/clients/android index 7d1e7c72..172199df 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 7d1e7c72cebdce23ea4f5f3dcfc8d12a4dc29633 +Subproject commit 172199dfc39be91ba95394b0dab20735a88ef33f diff --git a/clients/apple b/clients/apple index 80c86686..16800708 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 80c866861df86b43d597e86b086458ff8d6c103b +Subproject commit 16800708dd375d2582eec2388b92c1be76fe8343 diff --git a/docs/changelog.md b/docs/changelog.md index b67663e1..00dc3167 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,14 @@ icon: material/alert-decagram --- +#### 1.13.1 + +* Fixes and improvements + +#### 1.12.14 + +* Backport fixes + #### 1.13.0 Important changes since 1.12: From 84019b06d9b0757a02d8332bb011c353010bfb09 Mon Sep 17 00:00:00 2001 From: dyhkwong <50692134+dyhkwong@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:13:39 +0800 Subject: [PATCH 182/185] Fix v2ray HTTP transport server --- transport/v2rayhttp/server.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index 828c9f09..282c7c23 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -136,10 +136,12 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil) } else { writer.WriteHeader(http.StatusOK) + flusher := writer.(http.Flusher) + flusher.Flush() done := make(chan struct{}) conn := NewHTTP2Wrapper(&ServerHTTPConn{ NewHTTPConn(request.Body, writer), - writer.(http.Flusher), + flusher, }) s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) { close(done) From 27c5b0b1aff2cd77ac367982e7f308377eab42b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 6 Mar 2026 14:53:03 +0800 Subject: [PATCH 183/185] Fix DNS exchange failure and recursion deadlock in connector Co-authored-by: everyx --- dns/transport/connector.go | 110 +++++++++++-- dns/transport/connector_test.go | 263 ++++++++++++++++++++++++++++++++ transport/v2raygrpc/client.go | 2 +- transport/v2raygrpc/conn.go | 14 +- transport/v2raygrpc/server.go | 2 +- 5 files changed, 373 insertions(+), 18 deletions(-) create mode 100644 dns/transport/connector_test.go diff --git a/dns/transport/connector.go b/dns/transport/connector.go index 18fad0a5..769232f4 100644 --- a/dns/transport/connector.go +++ b/dns/transport/connector.go @@ -4,6 +4,9 @@ import ( "context" "net" "sync" + "time" + + E "github.com/sagernet/sing/common/exceptions" ) type ConnectorCallbacks[T any] struct { @@ -16,10 +19,11 @@ type Connector[T any] struct { dial func(ctx context.Context) (T, error) callbacks ConnectorCallbacks[T] - access sync.Mutex - connection T - hasConnection bool - connecting chan struct{} + access sync.Mutex + connection T + hasConnection bool + connectionCancel context.CancelFunc + connecting chan struct{} closeCtx context.Context closed bool @@ -47,6 +51,10 @@ func NewSingleflightConnector(closeCtx context.Context, dial func(context.Contex }) } +type contextKeyConnecting struct{} + +var errRecursiveConnectorDial = E.New("recursive connector dial") + func (c *Connector[T]) Get(ctx context.Context) (T, error) { var zero T for { @@ -64,6 +72,14 @@ func (c *Connector[T]) Get(ctx context.Context) (T, error) { } c.hasConnection = false + if c.connectionCancel != nil { + c.connectionCancel() + c.connectionCancel = nil + } + if isRecursiveConnectorDial(ctx, c) { + c.access.Unlock() + return zero, errRecursiveConnectorDial + } if c.connecting != nil { connecting := c.connecting @@ -79,10 +95,16 @@ func (c *Connector[T]) Get(ctx context.Context) (T, error) { } } + if err := ctx.Err(); err != nil { + c.access.Unlock() + return zero, err + } + c.connecting = make(chan struct{}) c.access.Unlock() - connection, err := c.dialWithCancellation(ctx) + dialContext := context.WithValue(ctx, contextKeyConnecting{}, c) + connection, cancel, err := c.dialWithCancellation(dialContext) c.access.Lock() close(c.connecting) @@ -94,13 +116,21 @@ func (c *Connector[T]) Get(ctx context.Context) (T, error) { } if c.closed { + cancel() c.callbacks.Close(connection) c.access.Unlock() return zero, ErrTransportClosed } + if err = ctx.Err(); err != nil { + cancel() + c.callbacks.Close(connection) + c.access.Unlock() + return zero, err + } c.connection = connection c.hasConnection = true + c.connectionCancel = cancel result := c.connection c.access.Unlock() @@ -108,19 +138,63 @@ func (c *Connector[T]) Get(ctx context.Context) (T, error) { } } -func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, error) { - dialCtx, cancel := context.WithCancel(ctx) - defer cancel() +func isRecursiveConnectorDial[T any](ctx context.Context, connector *Connector[T]) bool { + dialConnector, loaded := ctx.Value(contextKeyConnecting{}).(*Connector[T]) + return loaded && dialConnector == connector +} - go func() { - select { - case <-c.closeCtx.Done(): +func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, context.CancelFunc, error) { + var zero T + if err := ctx.Err(); err != nil { + return zero, nil, err + } + connCtx, cancel := context.WithCancel(c.closeCtx) + + var ( + stateAccess sync.Mutex + dialComplete bool + ) + stopCancel := context.AfterFunc(ctx, func() { + stateAccess.Lock() + if !dialComplete { cancel() - case <-dialCtx.Done(): } - }() + stateAccess.Unlock() + }) + select { + case <-ctx.Done(): + stateAccess.Lock() + dialComplete = true + stateAccess.Unlock() + stopCancel() + cancel() + return zero, nil, ctx.Err() + default: + } - return c.dial(dialCtx) + connection, err := c.dial(valueContext{connCtx, ctx}) + stateAccess.Lock() + dialComplete = true + stateAccess.Unlock() + stopCancel() + if err != nil { + cancel() + return zero, nil, err + } + return connection, cancel, nil +} + +type valueContext struct { + context.Context + parent context.Context +} + +func (v valueContext) Value(key any) any { + return v.parent.Value(key) +} + +func (v valueContext) Deadline() (time.Time, bool) { + return v.parent.Deadline() } func (c *Connector[T]) Close() error { @@ -132,6 +206,10 @@ func (c *Connector[T]) Close() error { } c.closed = true + if c.connectionCancel != nil { + c.connectionCancel() + c.connectionCancel = nil + } if c.hasConnection { c.callbacks.Close(c.connection) c.hasConnection = false @@ -144,6 +222,10 @@ func (c *Connector[T]) Reset() { c.access.Lock() defer c.access.Unlock() + if c.connectionCancel != nil { + c.connectionCancel() + c.connectionCancel = nil + } if c.hasConnection { c.callbacks.Reset(c.connection) c.hasConnection = false diff --git a/dns/transport/connector_test.go b/dns/transport/connector_test.go new file mode 100644 index 00000000..280e5da6 --- /dev/null +++ b/dns/transport/connector_test.go @@ -0,0 +1,263 @@ +package transport + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type testConnectorConnection struct{} + +func TestConnectorRecursiveGetFailsFast(t *testing.T) { + t.Parallel() + + var ( + dialCount atomic.Int32 + closeCount atomic.Int32 + connector *Connector[*testConnectorConnection] + ) + + dial := func(ctx context.Context) (*testConnectorConnection, error) { + dialCount.Add(1) + _, err := connector.Get(ctx) + if err != nil { + return nil, err + } + return &testConnectorConnection{}, nil + } + + connector = NewConnector(context.Background(), dial, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) { + closeCount.Add(1) + }, + Reset: func(connection *testConnectorConnection) { + closeCount.Add(1) + }, + }) + + _, err := connector.Get(context.Background()) + require.ErrorIs(t, err, errRecursiveConnectorDial) + require.EqualValues(t, 1, dialCount.Load()) + require.EqualValues(t, 0, closeCount.Load()) +} + +func TestConnectorRecursiveGetAcrossConnectorsAllowed(t *testing.T) { + t.Parallel() + + var ( + outerDialCount atomic.Int32 + innerDialCount atomic.Int32 + outerConnector *Connector[*testConnectorConnection] + innerConnector *Connector[*testConnectorConnection] + ) + + innerConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) { + innerDialCount.Add(1) + return &testConnectorConnection{}, nil + }, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) {}, + Reset: func(connection *testConnectorConnection) {}, + }) + + outerConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) { + outerDialCount.Add(1) + _, err := innerConnector.Get(ctx) + if err != nil { + return nil, err + } + return &testConnectorConnection{}, nil + }, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) {}, + Reset: func(connection *testConnectorConnection) {}, + }) + + _, err := outerConnector.Get(context.Background()) + require.NoError(t, err) + require.EqualValues(t, 1, outerDialCount.Load()) + require.EqualValues(t, 1, innerDialCount.Load()) +} + +func TestConnectorDialContextPreservesValueAndDeadline(t *testing.T) { + t.Parallel() + + type contextKey struct{} + + var ( + dialValue any + dialDeadline time.Time + dialHasDeadline bool + ) + + connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) { + dialValue = ctx.Value(contextKey{}) + dialDeadline, dialHasDeadline = ctx.Deadline() + return &testConnectorConnection{}, nil + }, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) {}, + Reset: func(connection *testConnectorConnection) {}, + }) + + deadline := time.Now().Add(time.Minute) + requestContext, cancel := context.WithDeadline(context.WithValue(context.Background(), contextKey{}, "test-value"), deadline) + defer cancel() + + _, err := connector.Get(requestContext) + require.NoError(t, err) + require.Equal(t, "test-value", dialValue) + require.True(t, dialHasDeadline) + require.WithinDuration(t, deadline, dialDeadline, time.Second) +} + +func TestConnectorDialSkipsCanceledRequest(t *testing.T) { + t.Parallel() + + var dialCount atomic.Int32 + connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) { + dialCount.Add(1) + return &testConnectorConnection{}, nil + }, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) {}, + Reset: func(connection *testConnectorConnection) {}, + }) + + requestContext, cancel := context.WithCancel(context.Background()) + cancel() + + _, err := connector.Get(requestContext) + require.ErrorIs(t, err, context.Canceled) + require.EqualValues(t, 0, dialCount.Load()) +} + +func TestConnectorCanceledRequestDoesNotCacheConnection(t *testing.T) { + t.Parallel() + + var ( + dialCount atomic.Int32 + closeCount atomic.Int32 + ) + dialStarted := make(chan struct{}, 1) + releaseDial := make(chan struct{}) + + connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) { + dialCount.Add(1) + select { + case dialStarted <- struct{}{}: + default: + } + <-releaseDial + return &testConnectorConnection{}, nil + }, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) { + closeCount.Add(1) + }, + Reset: func(connection *testConnectorConnection) {}, + }) + + requestContext, cancel := context.WithCancel(context.Background()) + result := make(chan error, 1) + go func() { + _, err := connector.Get(requestContext) + result <- err + }() + + <-dialStarted + cancel() + close(releaseDial) + + err := <-result + require.ErrorIs(t, err, context.Canceled) + require.EqualValues(t, 1, dialCount.Load()) + require.EqualValues(t, 1, closeCount.Load()) + + _, err = connector.Get(context.Background()) + require.NoError(t, err) + require.EqualValues(t, 2, dialCount.Load()) +} + +func TestConnectorDialContextNotCanceledByRequestContextAfterDial(t *testing.T) { + t.Parallel() + + var dialContext context.Context + connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) { + dialContext = ctx + return &testConnectorConnection{}, nil + }, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) {}, + Reset: func(connection *testConnectorConnection) {}, + }) + + requestContext, cancel := context.WithCancel(context.Background()) + _, err := connector.Get(requestContext) + require.NoError(t, err) + require.NotNil(t, dialContext) + + cancel() + + select { + case <-dialContext.Done(): + t.Fatal("dial context canceled by request context after successful dial") + case <-time.After(100 * time.Millisecond): + } + + err = connector.Close() + require.NoError(t, err) +} + +func TestConnectorDialContextCanceledOnClose(t *testing.T) { + t.Parallel() + + var dialContext context.Context + connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) { + dialContext = ctx + return &testConnectorConnection{}, nil + }, ConnectorCallbacks[*testConnectorConnection]{ + IsClosed: func(connection *testConnectorConnection) bool { + return false + }, + Close: func(connection *testConnectorConnection) {}, + Reset: func(connection *testConnectorConnection) {}, + }) + + _, err := connector.Get(context.Background()) + require.NoError(t, err) + require.NotNil(t, dialContext) + + select { + case <-dialContext.Done(): + t.Fatal("dial context canceled before connector close") + default: + } + + err = connector.Close() + require.NoError(t, err) + + select { + case <-dialContext.Done(): + case <-time.After(time.Second): + t.Fatal("dial context not canceled after connector close") + } +} diff --git a/transport/v2raygrpc/client.go b/transport/v2raygrpc/client.go index 2bbaa627..5af53856 100644 --- a/transport/v2raygrpc/client.go +++ b/transport/v2raygrpc/client.go @@ -106,7 +106,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { cancel(err) return nil, err } - return NewGRPCConn(stream), nil + return NewGRPCConn(stream, cancel), nil } func (c *Client) Close() error { diff --git a/transport/v2raygrpc/conn.go b/transport/v2raygrpc/conn.go index c29da4f9..87be9661 100644 --- a/transport/v2raygrpc/conn.go +++ b/transport/v2raygrpc/conn.go @@ -1,8 +1,10 @@ package v2raygrpc import ( + "context" "net" "os" + "sync" "time" "github.com/sagernet/sing/common/baderror" @@ -14,16 +16,19 @@ var _ net.Conn = (*GRPCConn)(nil) type GRPCConn struct { GunService - cache []byte + cache []byte + cancel context.CancelCauseFunc + closeOnce sync.Once } -func NewGRPCConn(service GunService) *GRPCConn { +func NewGRPCConn(service GunService, cancel context.CancelCauseFunc) *GRPCConn { //nolint:staticcheck if client, isClient := service.(GunService_TunClient); isClient { service = &clientConnWrapper{client} } return &GRPCConn{ GunService: service, + cancel: cancel, } } @@ -54,6 +59,11 @@ func (c *GRPCConn) Write(b []byte) (n int, err error) { } func (c *GRPCConn) Close() error { + c.closeOnce.Do(func() { + if c.cancel != nil { + c.cancel(nil) + } + }) return nil } diff --git a/transport/v2raygrpc/server.go b/transport/v2raygrpc/server.go index b6b13f82..4d426aa1 100644 --- a/transport/v2raygrpc/server.go +++ b/transport/v2raygrpc/server.go @@ -52,7 +52,7 @@ func NewServer(ctx context.Context, logger logger.ContextLogger, options option. } func (s *Server) Tun(server GunService_TunServer) error { - conn := NewGRPCConn(server) + conn := NewGRPCConn(server, nil) var source M.Socksaddr if remotePeer, loaded := peer.FromContext(server.Context()); loaded { source = M.SocksaddrFromNet(remotePeer.Addr) From 4e0a953b98b98320b0d8fc0cd1f1871c6c146e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 7 Mar 2026 15:44:29 +0800 Subject: [PATCH 184/185] sing: Revert "Relax domain name validation to support non-standard characters" --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 99f66670..c00a9a2d 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/sagernet/gomobile v0.1.12 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 - github.com/sagernet/sing v0.8.1 + github.com/sagernet/sing v0.8.2 github.com/sagernet/sing-mux v0.3.4 github.com/sagernet/sing-quic v0.6.0 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index b2f898b1..9348343a 100644 --- a/go.sum +++ b/go.sum @@ -236,8 +236,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o= github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= -github.com/sagernet/sing v0.8.1 h1:Li+zg4xdiMsvdX4j50TPqmSG8LF/TB9US2qlAN40izU= -github.com/sagernet/sing v0.8.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.2 h1:kX1IH9SWJv4S0T9M8O+HNahWgbOuY1VauxbF7NU5lOg= +github.com/sagernet/sing v0.8.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s= github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM= From c0d45aebfadae9988e160c761f32e9edf8e0d8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 7 Mar 2026 15:51:27 +0800 Subject: [PATCH 185/185] Bump version --- clients/android | 2 +- clients/apple | 2 +- docs/changelog.md | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/clients/android b/clients/android index 172199df..7777469b 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 172199dfc39be91ba95394b0dab20735a88ef33f +Subproject commit 7777469b5d21bc0312ed38bede457ee3128260e2 diff --git a/clients/apple b/clients/apple index 16800708..c19945f6 160000 --- a/clients/apple +++ b/clients/apple @@ -1 +1 @@ -Subproject commit 16800708dd375d2582eec2388b92c1be76fe8343 +Subproject commit c19945f65be76ae5d16fc684a166079877802641 diff --git a/docs/changelog.md b/docs/changelog.md index 00dc3167..29c48605 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,10 @@ icon: material/alert-decagram --- +#### 1.13.2 + +* Fixes and improvements + #### 1.13.1 * Fixes and improvements